Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support GitHub two factor authentication #536

Merged
merged 14 commits into from Feb 5, 2017

Conversation

fabianlupa
Copy link
Member

References #494
Fixes #253

This should work for now. General approach:

  1. Hook into authentication process before the login dialog appears
  2. If the repository URL is supported by one registered two factor authenticator an additional option is added to the selection screen to enter a 2FA token
  3. If a 2FA token is entered before the usual authentication, requests to the repository hoster's API are made to generate an access token using username, password and 2FA token
  4. If that succeeds the access token is used instead of the user's password for basic authentication. The token is also cached in the user settings.

And when leaving and reentering abapGit:

  1. Login required -> is two factor authentication supported and is there an access key cached for the hoster of the repository?
  2. Ask the user to unlock the access token
  3. Use the unlocked access token as the password again

The token can also be deleted from the login popup.

One thing that is still todo is encrypting the access token. Currently it is stored in abapGit's persistence layer in plain text which is not ideal on multi user development systems where anyone can access database tables directly. My idea was to symmetrically encrypt the access token using the user's password he entered for generating the token and decrypt it using the same password. abapGit does not need to validate the decryption because if the password is wrong and a wrong access key is decrypted authentication will fail anyways. But I could not find any standard function modules or classes that support symmetric encryption using a key. Any tips would be appreciated there. The two spots in code are marked with TODO.

Another thing is that it sometimes takes multiple tries for me to generate a new access token. Might be caused by my authenticator being out of sync or an API rate limit but also could be a programming error on my end.

I think I removed all the uses of >702 stuff but I am not sure how to test for that (my system is 750).

@larshp
Copy link
Member

larshp commented Jan 16, 2017

Hi,
Thanks!

I'm concerned that this will give the user a false sense of security. Perhaps add a popup explaining the user what actually happens, as this is not true 2FA as might be expected when looking at the GUI.

On the other hand, the current approach in abapGit does not work for GitHub users using 2FA, hmm

For encrypting the token I suggest using https://github.com/Sumu-Ning/AES just make it a dynamic binding so it is not required statically, and show a popup if the user needs to install it.

What 2FA setup have you used for testing? Something like Google Authenticator? I have a Yubikey, which sends a SMS code after entering the password if the Yubikey is not supported.

Do you know if there is a feature in the github API to check if the user has 2FA enabled? Perhaps it will be easier to guide the user to create the access token and enter it manually in abapGit settings?

@fabianlupa
Copy link
Member Author

Hi Lars,

true 2FA would be quite cumbersome to implement. Git over http(s) on GitHub does not support authentication using 2FA while GitHub's own API does, so one would have to use GitHub's API for all git requests if it is a GitHub repository which I doubt is worth it. I agree with you though, currently it is not apparent for what the token is used. An info button with a documentation popup next to the token input field could solve this I think.

I personally use the Google Authenticator app for several services supporting two factor authentication including GitHub. SMS should be solvable as well (or already works just you need two tries?). You can get the type of two factor auth the account uses by reading the X-GitHub-OTP: required; :2fa-type header you receive after trying to authenticate using only username and password in the 401 response (source).

Now that I think about it, a better work flow might be to try to authenticate just with the login data and if you get a 401 response with that specific header you reopen the popup with the entered data and additionally show the token field.

If you dislike the approach of creating an access token "in application": Using manually created access tokens as the password already works very well without this PR. The problem is just that you need to reenter it every time. If there was a password safe in general in abapGit independent of 2FA this problem would be solved. The current repository based login data would need to be moved up one level though to git hosting services, because otherwise you create access tokens / save login data for each repository even though they all use the same logins.

So which direction do you want to go?

@larshp
Copy link
Member

larshp commented Jan 16, 2017

hmm, not sure, need some time to consider.

Which direction do you prefer/recommend?

@larshp
Copy link
Member

larshp commented Jan 17, 2017

New idea: how about deleting the token immediately after use? That way it will not have to be stored in abapGit.

If abapGit dumps after having created the token, it will not delete it, but that might be okay?

@fabianlupa
Copy link
Member Author

Good idea, that seems to work best with the existing concept. We might even be able to delete the last used token when requesting a new one instead of immediately after use.

I also did some research on other git hosters. Gitlab and TFS support access tokens but I did not find an API to create one programmatically. Bitbucket does have one but it might require a browser based approach. So for these ones letting the user create a token on the respective website on his own and saving it in abapGit's not yet existing password storage would be the best solution in my opinion.

However, I think GitHub is by far the most used option of all these hosters so it does make sense to support its two factor authentication "natively". Also your idea is more in scope of the current approach of this PR while a password storage could/should be a separate one if it is still needed then.

So I would refactor this PR to achieve the following:

  • After returning from the password dialog with the users credentials check if the repository url is one of GitHub's
  • If that is the case send a login request using the credentials and check if two factor authentication is required using the X-GitHub-OTP: required; response header
  • If it is not required return from acquire_login_details and let normal authentication continue as before
  • If it is required show a new prompt for a token which can be obtained either from an authenticator app or by sms (which should have arrived by now because of the first login request)
  • Create a new access token using username, password and 2FA token (and maybe delete existing ones)
  • Return from acquire_login_details with the created access token as the password

Is that in line with your idea?

@larshp
Copy link
Member

larshp commented Jan 18, 2017

Yep, looks good to me 😄

@@ -494,6 +499,51 @@ CLASS lcl_http IMPLEMENTATION.
iv_username = lv_user ).
ENDIF.

" Is the repository hoster supported for using two factor authentication?
TRY.
IF lcl_2fa_authenticator_registry=>is_url_supported( iv_url ) = abap_true.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we take most of this part and move to a method in the 2fa include? and have it raise lcx_exception

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I'll get on that

@larshp
Copy link
Member

larshp commented Jan 22, 2017

sorry, merging #559 and changing all files in abapGit so you might need to rebase/fix some conflicts

@fabianlupa
Copy link
Member Author

No problem, rebase worked out fine I think. I'll comment again once I got the token deletion working.

@fabianlupa
Copy link
Member Author

Sorry for the delay!

  1. I moved the entry logic to 2FA into a static method.
  2. When using two factor authentication any previously generated access tokens are deleted before creating a new one. Actually only a single access token by abapGit can exist at any time in a GitHub account because the fingerprint attribute must be unique.

One thing I am mildly concerned about is the parsing logic for the list of all access tokens to filter out the one by abapGit (code). Since on 7.02 the new fancy JSON and REST apis do not exist yet (or I do not know the older ones?) I try to parse the correct token ID using a regular expression which is not ideal and might not handle all edge cases if the response structure is any different.

@larshp
Copy link
Member

larshp commented Feb 5, 2017

travis errors: fixed, I've released a new version of abaplint

parsing logic: worst case we have to adjust it, lets just ship it and see if it works out 😄

Copy link
Member

@larshp larshp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see comments


CLASS lcl_2fa_github_authenticator IMPLEMENTATION.
METHOD constructor.
super->constructor( 'https?:\/\/(www\.)?github.com.*$' ).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be "^https?://(www.)?github.com.*$" ? but I guess it does not matter much

" Try to authenticate, if 2FA is required there will be a specific response header
li_client->authenticate( username = iv_username password = iv_password ).
li_client->send( ).
li_client->receive( ).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dumps here, HTTP_COMMUNICATION_FAILURE, see comment for CREATE_BY_URL

cl_http_client=>create_by_url(
EXPORTING
url = gc_github_api_url
IMPORTING
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add following, I get a dump due to untrusted SSL certificates, as it currently does not use the same PSE(?) as the other abapGit requests

        ssl_id        = 'ANONYM'
        proxy_host    = lo_settings->get_proxy_url( )
        proxy_service = lo_settings->get_proxy_port( )

see example in include ZABAPGIT_HTTP

" Try to login to GitHub API with username, password and 2fa token
cl_http_client=>create_by_url(
EXPORTING
url = gc_github_api_url
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also add SSL_ID and proxy settings here

@larshp
Copy link
Member

larshp commented Feb 5, 2017

Using tokens as before still works

However I dont receive a SMS code, will look into why.

@larshp
Copy link
Member

larshp commented Feb 5, 2017

SMS codes are currently not triggered, it requires a real request to be sent, I've changed the code in method is_2fa_required and added below, it triggers an SMS(url changed, and http payload added)

    cl_http_client=>create_by_url(
      EXPORTING
        url    = `https://api.github.com/authorizations`
        ssl_id        = 'ANONYM'
      IMPORTING
        client = li_client ).

    li_client->request->set_data( cl_abap_codepage=>convert_to( '{"scopes": ["repo", "user"], "note": "getting-started"}' ) ).

also see https://gist.github.com/caspyin/2288960

@fabianlupa
Copy link
Member Author

Thanks for the review!
SMS should work again. Regarding SSL certificates: api.github.com has its own certificate (*.github.com) which I added in STRUST a while ago (and totally forgot about...). I don't think there is a way around having the user add it there manually, or is there?

@larshp
Copy link
Member

larshp commented Feb 5, 2017

STRUST: nope, no known workaround, will do some testing, if possible would like to not break stuff for existing users. Perhaps we need to update the wiki installation guide

METHOD set_new_token_request.
DATA: lv_json_string TYPE string.

lv_json_string = |\{"scopes":["repo"],"note":"abapGit","fingerprint":"abapGit2FA"\}|.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we change the description of the token to "Generated by abapGit" or "Generated by ZABAPGIT"?

@larshp
Copy link
Member

larshp commented Feb 5, 2017

SMS: I get the SMS code and it works. But the 2nd time does not work, as it has to delete the old token, guess the SMS codes are real one time codes.

Do you know if the li_http_client in delete_access_tokens is reused in li_authenticator->authenticate, then perhaps it does not need the code again, or the same code can be reused as it is in the same HTTP session, currently it is 2 different sessions

@larshp
Copy link
Member

larshp commented Feb 5, 2017

SSL certificates: looks like api.github.com works with the main github.com certificate, so everything okay :)

@fabianlupa
Copy link
Member Author

Can you try again now if the one SMS code is used for both the deletion and the creation request?
And sorry for breaking your definitions-at-top-check twice...

@fabianlupa
Copy link
Member Author

Also I had to add the *.github.com certificate in addition to the normal one in STRUST for it to work. I guess we have configured the normal certificate differently? If anyone has the same configuration as me this would be a breaking change because is_2fa_required will perform the check for any repository on GitHub and raise LCX_2FA_COMMUNICATION_ERROR if the connection is not trusted.

I just read Installing abapGit and therefore assume you imported the root certificate in STRUST while I just imported the two "end of the chain" github certificates. Not sure how many others did it the way I did. Might want to have is_2fa_required fail silently.

@larshp
Copy link
Member

larshp commented Feb 5, 2017

STRUST: yeah, the top certificate is enough

Now it works when I have a breakpoint, and occasionally when pushing. I think we need to add a WAIT UP TO 1 SECONDS. after creating the token, as it most of the time does not seem to work immediately after it is created, GitHub needs time to process the request

definitions-at-top: it is a but in abaplint, will fix it in a bit. However I'm also a bit afraid of using STATICS here, perhaps make it a member attribute and clear it whenever method use_2fa_if_required is called?

Copy link
Member

@larshp larshp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also see comment above

RAISE EXCEPTION TYPE lcx_2fa_token_gen_failed
EXPORTING
iv_error_text = 'Token generation failed: parser error' ##NO_TEXT.
ENDIF.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

think we need to add a WAIT UP TO 1 SECONDS. here, as Github needs to finish saving the token before it is used

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting that GitHub returns the token before it is fully persisted and ready to use. I also don't have that problem on my end.
I'd prefer adding RZL_SLEEP instead of WAIT to avoid the implicit commit.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, I guess the token has to be distributed to the other servers in the cluster. But it seems like this is the issue,

RZL_SLEEP will also do the trick

@larshp
Copy link
Member

larshp commented Feb 5, 2017

tested ok, now it seems to work every time, merging 😄

thanks @flaiker

@larshp larshp merged commit fc84127 into abapGit:master Feb 5, 2017
@larshp
Copy link
Member

larshp commented Feb 5, 2017

@flaiker are you on twitter?

@larshp larshp mentioned this pull request Feb 5, 2017
@fabianlupa fabianlupa deleted the github_2fa_v2 branch February 5, 2017 16:00
@fabianlupa
Copy link
Member Author

@larshp My pleasure! And no I am not, some bot took my name a while ago and since then I am averted :P

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants