Lcl proxy auth (#1198)

* lcl_proxy_auth to global

* lcx_2fa_error to global

* 2FA to global
This commit is contained in:
Lars Hvam 2018-02-11 13:45:36 +01:00 committed by GitHub
parent 600b69b707
commit 540ebe16e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 1364 additions and 847 deletions

View File

@ -0,0 +1,110 @@
"! Default <em>LIF_2FA-AUTHENTICATOR</em> implememtation
CLASS zcl_abapgit_2fa_auth_base DEFINITION
PUBLIC
ABSTRACT
CREATE PUBLIC .
PUBLIC SECTION.
INTERFACES:
zif_abapgit_2fa_authenticator.
ALIASES:
authenticate FOR zif_abapgit_2fa_authenticator~authenticate,
supports_url FOR zif_abapgit_2fa_authenticator~supports_url,
get_service_id_from_url FOR zif_abapgit_2fa_authenticator~get_service_id_from_url,
is_2fa_required FOR zif_abapgit_2fa_authenticator~is_2fa_required,
delete_access_tokens FOR zif_abapgit_2fa_authenticator~delete_access_tokens,
begin FOR zif_abapgit_2fa_authenticator~begin,
end FOR zif_abapgit_2fa_authenticator~end.
METHODS:
"! @parameter iv_supported_url_regex | Regular expression to check if a repository url is
"! supported, used for default implementation of
"! <em>SUPPORTS_URL</em>
constructor IMPORTING iv_supported_url_regex TYPE clike.
PROTECTED SECTION.
CLASS-METHODS:
"! Helper method to raise class based exception after traditional exception was raised
"! <p>
"! <em>sy-msg...</em> must be set right before calling!
"! </p>
raise_comm_error_from_sy RAISING zcx_abapgit_2fa_comm_error.
METHODS:
"! @parameter rv_running | Internal session is currently active
is_session_running RETURNING VALUE(rv_running) TYPE abap_bool.
PRIVATE SECTION.
DATA:
mo_url_regex TYPE REF TO cl_abap_regex,
mv_session_running TYPE abap_bool.
ENDCLASS.
CLASS ZCL_ABAPGIT_2FA_AUTH_BASE IMPLEMENTATION.
METHOD constructor.
CREATE OBJECT mo_url_regex
EXPORTING
pattern = iv_supported_url_regex
ignore_case = abap_true.
ENDMETHOD.
METHOD is_session_running.
rv_running = mv_session_running.
ENDMETHOD.
METHOD raise_comm_error_from_sy.
DATA: lv_error_msg TYPE string.
MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno
WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4
INTO lv_error_msg.
RAISE EXCEPTION TYPE zcx_abapgit_2fa_comm_error
EXPORTING
mv_text = |Communication error: { lv_error_msg }| ##NO_TEXT.
ENDMETHOD.
METHOD authenticate.
RAISE EXCEPTION TYPE zcx_abapgit_2fa_auth_failed. " Needs to be overwritten in subclasses
ENDMETHOD.
METHOD begin.
IF mv_session_running = abap_true.
RAISE EXCEPTION TYPE zcx_abapgit_2fa_illegal_state.
ENDIF.
mv_session_running = abap_true.
ENDMETHOD.
METHOD delete_access_tokens.
RAISE EXCEPTION TYPE zcx_abapgit_2fa_del_failed. " Needs to be overwritten in subclasses
ENDMETHOD.
METHOD end.
IF mv_session_running = abap_false.
RAISE EXCEPTION TYPE zcx_abapgit_2fa_illegal_state.
ENDIF.
mv_session_running = abap_false.
ENDMETHOD.
METHOD get_service_id_from_url.
rv_id = 'UNKNOWN SERVICE'. " Please overwrite in subclasses
ENDMETHOD.
METHOD is_2fa_required.
rv_required = abap_false.
ENDMETHOD.
METHOD supports_url.
rv_supported = mo_url_regex->create_matcher( text = iv_url )->match( ).
ENDMETHOD.
ENDCLASS.

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<abapGit version="v1.0.0" serializer="LCL_OBJECT_CLAS" serializer_version="v1.0.0">
<asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0">
<asx:values>
<VSEOCLASS>
<CLSNAME>ZCL_ABAPGIT_2FA_AUTH_BASE</CLSNAME>
<VERSION>1</VERSION>
<LANGU>E</LANGU>
<DESCRIPT>2FA base</DESCRIPT>
<EXPOSURE>2</EXPOSURE>
<STATE>1</STATE>
<CLSABSTRCT>X</CLSABSTRCT>
<CLSCCINCL>X</CLSCCINCL>
<FIXPT>X</FIXPT>
<UNICODE>X</UNICODE>
</VSEOCLASS>
</asx:values>
</asx:abap>
</abapGit>

View File

@ -0,0 +1,179 @@
"! Static registry class to find <em>LIF_2FA_AUTHENTICATOR</em> instances
CLASS zcl_abapgit_2fa_auth_registry DEFINITION
PUBLIC
FINAL
CREATE PRIVATE .
PUBLIC SECTION.
CLASS-METHODS:
class_constructor,
"! Retrieve an authenticator instance by url
"! @parameter iv_url | Url of the repository / service
"! @parameter ro_authenticator | Found authenticator instance
"! @raising lcx_2fa_unsupported | No authenticator found that supports the service
get_authenticator_for_url IMPORTING iv_url TYPE string
RETURNING VALUE(ro_authenticator) TYPE REF TO zif_abapgit_2fa_authenticator
RAISING zcx_abapgit_2fa_unsupported,
"! Check if there is a two factor authenticator available for the url
"! @parameter iv_url | Url of the repository / service
"! @parameter rv_supported | 2FA is supported
is_url_supported IMPORTING iv_url TYPE string
RETURNING VALUE(rv_supported) TYPE abap_bool,
"! Offer to use two factor authentication if supported and required
"! <p>
"! This uses GUI functionality to display a popup to request the user to enter a two factor
"! token. Also an dummy authentication request might be used to find out if two factor
"! authentication is required for the account.
"! </p>
"! @parameter iv_url | Url of the repository / service
"! @parameter cv_username | Username
"! @parameter cv_password | Password, will be replaced by an access token if two factor
"! authentication succeeds
"! @raising zcx_abapgit_exception | Error in two factor authentication
use_2fa_if_required IMPORTING iv_url TYPE string
CHANGING cv_username TYPE string
cv_password TYPE string
RAISING zcx_abapgit_exception.
CLASS-DATA:
"! All authenticators managed by the registry
gt_registered_authenticators TYPE HASHED TABLE OF REF TO zif_abapgit_2fa_authenticator
WITH UNIQUE KEY table_line READ-ONLY.
PRIVATE SECTION.
CLASS-METHODS:
popup_token
RETURNING VALUE(rv_token) TYPE string
RAISING zcx_abapgit_exception.
ENDCLASS.
CLASS ZCL_ABAPGIT_2FA_AUTH_REGISTRY IMPLEMENTATION.
METHOD class_constructor.
DEFINE _register.
CREATE OBJECT li_authenticator TYPE &1.
INSERT li_authenticator INTO TABLE gt_registered_authenticators.
END-OF-DEFINITION.
DATA: li_authenticator TYPE REF TO zif_abapgit_2fa_authenticator.
" If there are new authenticators these need to be added here manually.
" I do not think there is an equivalent to SEO_INTERFACE_IMPLEM_GET_ALL for local classes
" without invoking the compiler directly.
_register: zcl_abapgit_2fa_github_auth.
ENDMETHOD.
METHOD get_authenticator_for_url.
FIELD-SYMBOLS: <lo_authenticator> LIKE LINE OF gt_registered_authenticators.
LOOP AT gt_registered_authenticators ASSIGNING <lo_authenticator>.
IF <lo_authenticator>->supports_url( iv_url ) = abap_true.
ro_authenticator = <lo_authenticator>.
RETURN.
ENDIF.
ENDLOOP.
RAISE EXCEPTION TYPE zcx_abapgit_2fa_unsupported.
ENDMETHOD.
METHOD is_url_supported.
TRY.
get_authenticator_for_url( iv_url ).
rv_supported = abap_true.
CATCH zcx_abapgit_2fa_unsupported ##NO_HANDLER.
ENDTRY.
ENDMETHOD.
METHOD popup_token.
DATA: lv_returncode TYPE c,
lt_fields TYPE TABLE OF sval.
FIELD-SYMBOLS: <ls_field> LIKE LINE OF lt_fields.
APPEND INITIAL LINE TO lt_fields ASSIGNING <ls_field>.
<ls_field>-tabname = 'TADIR'.
<ls_field>-fieldname = 'OBJ_NAME'.
<ls_field>-fieldtext = 'Two factor auth. token'.
CALL FUNCTION 'POPUP_GET_VALUES'
EXPORTING
no_value_check = abap_true
popup_title = 'Two factor auth. token'
IMPORTING
returncode = lv_returncode
TABLES
fields = lt_fields
EXCEPTIONS
error_in_fields = 1
OTHERS = 2. "#EC NOTEXT
IF sy-subrc <> 0.
zcx_abapgit_exception=>raise( 'Error from POPUP_GET_VALUES' ).
ENDIF.
IF lv_returncode = 'A'.
zcx_abapgit_exception=>raise( 'Authentication cancelled' ).
ENDIF.
READ TABLE lt_fields INDEX 1 ASSIGNING <ls_field>.
ASSERT sy-subrc = 0.
rv_token = <ls_field>-value.
ENDMETHOD.
METHOD use_2fa_if_required.
DATA: li_authenticator TYPE REF TO zif_abapgit_2fa_authenticator,
lv_2fa_token TYPE string,
lv_access_token TYPE string,
lx_ex TYPE REF TO cx_root.
IF is_url_supported( iv_url ) = abap_false.
RETURN.
ENDIF.
TRY.
li_authenticator = get_authenticator_for_url( iv_url ).
li_authenticator->begin( ).
" Is two factor authentication required for this account?
IF li_authenticator->is_2fa_required( iv_url = iv_url
iv_username = cv_username
iv_password = cv_password ) = abap_true.
lv_2fa_token = popup_token( ).
" Delete an old access token if it exists
li_authenticator->delete_access_tokens( iv_url = iv_url
iv_username = cv_username
iv_password = cv_password
iv_2fa_token = lv_2fa_token ).
" Get a new access token
lv_access_token = li_authenticator->authenticate( iv_url = iv_url
iv_username = cv_username
iv_password = cv_password
iv_2fa_token = lv_2fa_token ).
" Use the access token instead of the password
cv_password = lv_access_token.
ENDIF.
li_authenticator->end( ).
CATCH zcx_abapgit_2fa_error INTO lx_ex.
TRY.
li_authenticator->end( ).
CATCH zcx_abapgit_2fa_illegal_state ##NO_HANDLER.
ENDTRY.
zcx_abapgit_exception=>raise( |2FA error: { lx_ex->get_text( ) }| ).
ENDTRY.
ENDMETHOD.
ENDCLASS.

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<abapGit version="v1.0.0" serializer="LCL_OBJECT_CLAS" serializer_version="v1.0.0">
<asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0">
<asx:values>
<VSEOCLASS>
<CLSNAME>ZCL_ABAPGIT_2FA_AUTH_REGISTRY</CLSNAME>
<VERSION>1</VERSION>
<LANGU>E</LANGU>
<DESCRIPT>2FA registry</DESCRIPT>
<STATE>1</STATE>
<CLSFINAL>X</CLSFINAL>
<CLSCCINCL>X</CLSCCINCL>
<FIXPT>X</FIXPT>
<UNICODE>X</UNICODE>
</VSEOCLASS>
</asx:values>
</asx:abap>
</abapGit>

View File

@ -0,0 +1,361 @@
CLASS zcl_abapgit_2fa_github_auth DEFINITION
PUBLIC
INHERITING FROM zcl_abapgit_2fa_auth_base
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
METHODS:
constructor,
get_service_id_from_url REDEFINITION,
authenticate REDEFINITION,
is_2fa_required REDEFINITION,
delete_access_tokens REDEFINITION,
end REDEFINITION.
PROTECTED SECTION.
PRIVATE SECTION.
CONSTANTS:
gc_github_api_url TYPE string VALUE `https://api.github.com/`,
gc_otp_header_name TYPE string VALUE `X-Github-OTP`,
gc_restendpoint_authorizations TYPE string VALUE `/authorizations`.
CLASS-METHODS:
set_new_token_request IMPORTING ii_request TYPE REF TO if_http_request,
get_token_from_response IMPORTING ii_response TYPE REF TO if_http_response
RETURNING VALUE(rv_token) TYPE string,
set_list_token_request IMPORTING ii_request TYPE REF TO if_http_request,
get_tobedel_tokens_from_resp IMPORTING ii_response TYPE REF TO if_http_response
RETURNING VALUE(rt_ids) TYPE stringtab,
set_del_token_request IMPORTING ii_request TYPE REF TO if_http_request
iv_token_id TYPE string.
METHODS:
get_authenticated_client IMPORTING iv_username TYPE string
iv_password TYPE string
iv_2fa_token TYPE string
RETURNING VALUE(ri_client) TYPE REF TO if_http_client
RAISING zcx_abapgit_2fa_auth_failed
zcx_abapgit_2fa_comm_error.
DATA:
mi_authenticated_session TYPE REF TO if_http_client.
ENDCLASS.
CLASS ZCL_ABAPGIT_2FA_GITHUB_AUTH IMPLEMENTATION.
METHOD constructor.
super->constructor( '^https?://(www\.)?github.com.*$' ).
ENDMETHOD.
METHOD get_authenticated_client.
DATA: lv_http_code TYPE i,
lv_http_code_description TYPE string,
lo_settings TYPE REF TO zcl_abapgit_settings.
" If there is a cached client return it instead
IF is_session_running( ) = abap_true AND mi_authenticated_session IS BOUND.
ri_client = mi_authenticated_session.
RETURN.
ENDIF.
" Try to login to GitHub API with username, password and 2fa token
lo_settings = zcl_abapgit_persist_settings=>get_instance( )->read( ).
cl_http_client=>create_by_url(
EXPORTING
url = gc_github_api_url
ssl_id = 'ANONYM'
proxy_host = lo_settings->get_proxy_url( )
proxy_service = lo_settings->get_proxy_port( )
IMPORTING
client = ri_client
EXCEPTIONS
argument_not_found = 1
plugin_not_active = 2
internal_error = 3
OTHERS = 4 ).
IF sy-subrc <> 0.
raise_comm_error_from_sy( ).
ENDIF.
" https://developer.github.com/v3/auth/#working-with-two-factor-authentication
ri_client->propertytype_accept_cookie = if_http_client=>co_enabled.
ri_client->request->set_header_field( name = gc_otp_header_name value = iv_2fa_token ).
ri_client->authenticate( username = iv_username password = iv_password ).
ri_client->propertytype_logon_popup = if_http_client=>co_disabled.
ri_client->send( EXCEPTIONS OTHERS = 1 ).
IF sy-subrc <> 0.
raise_comm_error_from_sy( ).
ENDIF.
ri_client->receive( EXCEPTIONS OTHERS = 1 ).
IF sy-subrc <> 0.
raise_comm_error_from_sy( ).
ENDIF.
" Check if authentication has succeeded
ri_client->response->get_status(
IMPORTING
code = lv_http_code
reason = lv_http_code_description ).
IF lv_http_code <> 200.
RAISE EXCEPTION TYPE zcx_abapgit_2fa_auth_failed
EXPORTING
mv_text = |Authentication failed: { lv_http_code_description }|.
ENDIF.
" Cache the authenticated http session / client to avoid unnecessary additional authentication
IF is_session_running( ) = abap_true.
mi_authenticated_session = ri_client.
ENDIF.
ENDMETHOD.
METHOD get_tobedel_tokens_from_resp.
CONSTANTS: lc_search_regex TYPE string
VALUE `\{"id": ?(\d+)[^\{]*"app":\{[^\{^\}]*\}[^\{]*"fingerprint": ?` &
`"abapGit2FA"[^\{]*\}`.
DATA: lv_response TYPE string,
lo_regex TYPE REF TO cl_abap_regex,
lo_matcher TYPE REF TO cl_abap_matcher.
lv_response = cl_abap_codepage=>convert_from( ii_response->get_data( ) ).
CREATE OBJECT lo_regex
EXPORTING
pattern = lc_search_regex.
lo_matcher = lo_regex->create_matcher( text = lv_response ).
WHILE lo_matcher->find_next( ) = abap_true.
APPEND lo_matcher->get_submatch( 1 ) TO rt_ids.
ENDWHILE.
ENDMETHOD.
METHOD get_token_from_response.
CONSTANTS: lc_search_regex TYPE string VALUE `.*"token":"([^"]*).*$`.
DATA: lv_response TYPE string,
lo_regex TYPE REF TO cl_abap_regex,
lo_matcher TYPE REF TO cl_abap_matcher.
lv_response = cl_abap_codepage=>convert_from( ii_response->get_data( ) ).
CREATE OBJECT lo_regex
EXPORTING
pattern = lc_search_regex.
lo_matcher = lo_regex->create_matcher( text = lv_response ).
IF lo_matcher->match( ) = abap_true.
rv_token = lo_matcher->get_submatch( 1 ).
ENDIF.
ENDMETHOD.
METHOD set_del_token_request.
DATA: lv_url TYPE string.
lv_url = |{ gc_restendpoint_authorizations }/{ iv_token_id }|.
ii_request->set_header_field( name = if_http_header_fields_sap=>request_uri
value = lv_url ).
" Other methods than POST and GET do not have constants unfortunately
" ii_request->set_method( if_http_request=>co_request_method_delete ).
ii_request->set_method( 'DELETE' ).
ENDMETHOD.
METHOD set_list_token_request.
ii_request->set_header_field( name = if_http_header_fields_sap=>request_uri
value = gc_restendpoint_authorizations ).
ii_request->set_method( if_http_request=>co_request_method_get ).
ENDMETHOD.
METHOD set_new_token_request.
DATA: lv_json_string TYPE string.
lv_json_string = `{"scopes":["repo"],"note":"Generated by abapGit","fingerprint":"abapGit2FA"}`.
ii_request->set_data( cl_abap_codepage=>convert_to( lv_json_string ) ).
ii_request->set_header_field( name = if_http_header_fields_sap=>request_uri
value = gc_restendpoint_authorizations ).
ii_request->set_method( if_http_request=>co_request_method_post ).
ENDMETHOD.
METHOD authenticate.
DATA: li_http_client TYPE REF TO if_http_client,
lv_http_code TYPE i,
lv_http_code_description TYPE string.
" 1. Try to login to GitHub API
li_http_client = get_authenticated_client( iv_username = iv_username
iv_password = iv_password
iv_2fa_token = iv_2fa_token ).
" 2. Create an access token which can be used instead of a password
" https://developer.github.com/v3/oauth_authorizations/#create-a-new-authorization
set_new_token_request( ii_request = li_http_client->request ).
li_http_client->send( EXCEPTIONS OTHERS = 1 ).
IF sy-subrc <> 0.
raise_comm_error_from_sy( ).
ENDIF.
li_http_client->receive( EXCEPTIONS OTHERS = 1 ).
IF sy-subrc <> 0.
raise_comm_error_from_sy( ).
ENDIF.
li_http_client->response->get_status(
IMPORTING
code = lv_http_code
reason = lv_http_code_description ).
IF lv_http_code <> 201.
RAISE EXCEPTION TYPE zcx_abapgit_2fa_gen_failed
EXPORTING
mv_text = |Token generation failed: { lv_http_code } { lv_http_code_description }|.
ENDIF.
rv_access_token = get_token_from_response( li_http_client->response ).
IF rv_access_token IS INITIAL.
RAISE EXCEPTION TYPE zcx_abapgit_2fa_gen_failed
EXPORTING
mv_text = 'Token generation failed: parser error' ##NO_TEXT.
ENDIF.
" GitHub might need some time until the new token is ready to use, give it a second
CALL FUNCTION 'RZL_SLEEP'.
ENDMETHOD.
METHOD delete_access_tokens.
DATA: li_http_client TYPE REF TO if_http_client,
lv_http_code TYPE i,
lv_http_code_description TYPE string,
lt_tobedeleted_tokens TYPE stringtab.
FIELD-SYMBOLS: <lv_id> TYPE string.
li_http_client = get_authenticated_client( iv_username = iv_username
iv_password = iv_password
iv_2fa_token = iv_2fa_token ).
set_list_token_request( li_http_client->request ).
li_http_client->send( EXCEPTIONS OTHERS = 1 ).
IF sy-subrc <> 0.
raise_comm_error_from_sy( ).
ENDIF.
li_http_client->receive( EXCEPTIONS OTHERS = 1 ).
IF sy-subrc <> 0.
raise_comm_error_from_sy( ).
ENDIF.
li_http_client->response->get_status(
IMPORTING
code = lv_http_code
reason = lv_http_code_description ).
IF lv_http_code <> 200.
RAISE EXCEPTION TYPE zcx_abapgit_2fa_del_failed
EXPORTING
mv_text = |Could not fetch current 2FA authorizations: | &&
|{ lv_http_code } { lv_http_code_description }|.
ENDIF.
lt_tobedeleted_tokens = get_tobedel_tokens_from_resp( li_http_client->response ).
LOOP AT lt_tobedeleted_tokens ASSIGNING <lv_id> WHERE table_line IS NOT INITIAL.
set_del_token_request( ii_request = li_http_client->request
iv_token_id = <lv_id> ).
li_http_client->send( EXCEPTIONS OTHERS = 1 ).
IF sy-subrc <> 0.
raise_comm_error_from_sy( ).
ENDIF.
li_http_client->receive( EXCEPTIONS OTHERS = 1 ).
IF sy-subrc <> 0.
raise_comm_error_from_sy( ).
ENDIF.
li_http_client->response->get_status(
IMPORTING
code = lv_http_code
reason = lv_http_code_description ).
IF lv_http_code <> 204.
RAISE EXCEPTION TYPE zcx_abapgit_2fa_del_failed
EXPORTING
mv_text = |Could not delete token '{ <lv_id> }': | &&
|{ lv_http_code } { lv_http_code_description }|.
ENDIF.
ENDLOOP.
ENDMETHOD.
METHOD end.
super->end( ).
FREE mi_authenticated_session.
ENDMETHOD.
METHOD get_service_id_from_url.
rv_id = 'github'.
ENDMETHOD.
METHOD is_2fa_required.
DATA: li_client TYPE REF TO if_http_client,
lo_proxy TYPE REF TO zcl_abapgit_proxy_config.
CREATE OBJECT lo_proxy.
cl_http_client=>create_by_url(
EXPORTING
url = gc_github_api_url
ssl_id = 'ANONYM'
proxy_host = lo_proxy->get_proxy_url( )
proxy_service = lo_proxy->get_proxy_port( )
IMPORTING
client = li_client
EXCEPTIONS
argument_not_found = 1
plugin_not_active = 2
internal_error = 3
OTHERS = 4 ).
IF sy-subrc <> 0.
raise_comm_error_from_sy( ).
ENDIF.
li_client->propertytype_logon_popup = if_http_client=>co_disabled.
" The request needs to use something other than GET and it needs to be send to an endpoint
" to trigger a SMS.
li_client->request->set_header_field( name = if_http_header_fields_sap=>request_uri
value = gc_restendpoint_authorizations ).
li_client->request->set_method( if_http_request=>co_request_method_post ).
" 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( EXCEPTIONS OTHERS = 1 ).
IF sy-subrc <> 0.
raise_comm_error_from_sy( ).
ENDIF.
li_client->receive( EXCEPTIONS OTHERS = 1 ).
IF sy-subrc <> 0.
raise_comm_error_from_sy( ).
ENDIF.
" The response will either be UNAUTHORIZED or MALFORMED which is both fine.
IF li_client->response->get_header_field( gc_otp_header_name ) CP 'required*'.
rv_required = abap_true.
ENDIF.
ENDMETHOD.
ENDCLASS.

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<abapGit version="v1.0.0" serializer="LCL_OBJECT_CLAS" serializer_version="v1.0.0">
<asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0">
<asx:values>
<VSEOCLASS>
<CLSNAME>ZCL_ABAPGIT_2FA_GITHUB_AUTH</CLSNAME>
<VERSION>1</VERSION>
<LANGU>E</LANGU>
<DESCRIPT>2FA for github</DESCRIPT>
<EXPOSURE>2</EXPOSURE>
<STATE>1</STATE>
<CLSFINAL>X</CLSFINAL>
<CLSCCINCL>X</CLSCCINCL>
<FIXPT>X</FIXPT>
<UNICODE>X</UNICODE>
</VSEOCLASS>
</asx:values>
</asx:abap>
</abapGit>

View File

@ -0,0 +1,53 @@
CLASS zcl_abapgit_proxy_auth DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
CLASS-METHODS:
run
IMPORTING ii_client TYPE REF TO if_http_client
RAISING zcx_abapgit_exception.
PRIVATE SECTION.
CLASS-DATA: gv_username TYPE string,
gv_password TYPE string.
CLASS-METHODS: enter RAISING zcx_abapgit_exception.
ENDCLASS.
CLASS ZCL_ABAPGIT_PROXY_AUTH IMPLEMENTATION.
METHOD enter.
zcl_abapgit_password_dialog=>popup(
EXPORTING
iv_repo_url = 'Proxy Authentication'
CHANGING
cv_user = gv_username
cv_pass = gv_password ).
IF gv_username IS INITIAL OR gv_password IS INITIAL.
zcx_abapgit_exception=>raise( 'Proxy auth failed' ).
ENDIF.
ENDMETHOD.
METHOD run.
IF gv_username IS INITIAL OR gv_password IS INITIAL.
enter( ).
ENDIF.
ii_client->authenticate(
proxy_authentication = abap_true
username = gv_username
password = gv_password ).
ENDMETHOD.
ENDCLASS.

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<abapGit version="v1.0.0" serializer="LCL_OBJECT_CLAS" serializer_version="v1.0.0">
<asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0">
<asx:values>
<VSEOCLASS>
<CLSNAME>ZCL_ABAPGIT_PROXY_AUTH</CLSNAME>
<VERSION>1</VERSION>
<LANGU>E</LANGU>
<DESCRIPT>Proxy auth</DESCRIPT>
<EXPOSURE>2</EXPOSURE>
<STATE>1</STATE>
<CLSFINAL>X</CLSFINAL>
<CLSCCINCL>X</CLSCCINCL>
<FIXPT>X</FIXPT>
<UNICODE>X</UNICODE>
</VSEOCLASS>
</asx:values>
</asx:abap>
</abapGit>

View File

@ -0,0 +1,39 @@
class ZCX_ABAPGIT_2FA_AUTH_FAILED definition
public
inheriting from ZCX_ABAPGIT_2FA_ERROR
final
create public .
public section.
methods CONSTRUCTOR
importing
!TEXTID like TEXTID optional
!PREVIOUS like PREVIOUS optional
!MV_TEXT type STRING optional .
protected section.
methods GET_DEFAULT_TEXT
redefinition .
private section.
ENDCLASS.
CLASS ZCX_ABAPGIT_2FA_AUTH_FAILED IMPLEMENTATION.
method CONSTRUCTOR.
CALL METHOD SUPER->CONSTRUCTOR
EXPORTING
TEXTID = TEXTID
PREVIOUS = PREVIOUS
MV_TEXT = MV_TEXT
.
endmethod.
METHOD get_default_text.
rv_text = 'Authentication failed using 2FA.' ##NO_TEXT.
ENDMETHOD.
ENDCLASS.

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<abapGit version="v1.0.0" serializer="LCL_OBJECT_CLAS" serializer_version="v1.0.0">
<asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0">
<asx:values>
<VSEOCLASS>
<CLSNAME>ZCX_ABAPGIT_2FA_AUTH_FAILED</CLSNAME>
<VERSION>1</VERSION>
<LANGU>E</LANGU>
<DESCRIPT>Auth failed</DESCRIPT>
<CATEGORY>40</CATEGORY>
<EXPOSURE>2</EXPOSURE>
<STATE>1</STATE>
<CLSFINAL>X</CLSFINAL>
<CLSCCINCL>X</CLSCCINCL>
<FIXPT>X</FIXPT>
<UNICODE>X</UNICODE>
</VSEOCLASS>
<DESCRIPTIONS>
<SEOCOMPOTX>
<CLSNAME>ZCX_ABAPGIT_2FA_AUTH_FAILED</CLSNAME>
<CMPNAME>CONSTRUCTOR</CMPNAME>
<LANGU>E</LANGU>
<DESCRIPT>CONSTRUCTOR</DESCRIPT>
</SEOCOMPOTX>
</DESCRIPTIONS>
</asx:values>
</asx:abap>
</abapGit>

View File

@ -0,0 +1,39 @@
class ZCX_ABAPGIT_2FA_COMM_ERROR definition
public
inheriting from ZCX_ABAPGIT_2FA_ERROR
final
create public .
public section.
methods CONSTRUCTOR
importing
!TEXTID like TEXTID optional
!PREVIOUS like PREVIOUS optional
!MV_TEXT type STRING optional .
protected section.
methods GET_DEFAULT_TEXT
redefinition .
private section.
ENDCLASS.
CLASS ZCX_ABAPGIT_2FA_COMM_ERROR IMPLEMENTATION.
method CONSTRUCTOR.
CALL METHOD SUPER->CONSTRUCTOR
EXPORTING
TEXTID = TEXTID
PREVIOUS = PREVIOUS
MV_TEXT = MV_TEXT
.
endmethod.
METHOD get_default_text.
rv_text = 'Communication error.' ##NO_TEXT.
ENDMETHOD.
ENDCLASS.

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<abapGit version="v1.0.0" serializer="LCL_OBJECT_CLAS" serializer_version="v1.0.0">
<asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0">
<asx:values>
<VSEOCLASS>
<CLSNAME>ZCX_ABAPGIT_2FA_COMM_ERROR</CLSNAME>
<VERSION>1</VERSION>
<LANGU>E</LANGU>
<DESCRIPT>comm error</DESCRIPT>
<CATEGORY>40</CATEGORY>
<EXPOSURE>2</EXPOSURE>
<STATE>1</STATE>
<CLSFINAL>X</CLSFINAL>
<CLSCCINCL>X</CLSCCINCL>
<FIXPT>X</FIXPT>
<UNICODE>X</UNICODE>
</VSEOCLASS>
<DESCRIPTIONS>
<SEOCOMPOTX>
<CLSNAME>ZCX_ABAPGIT_2FA_COMM_ERROR</CLSNAME>
<CMPNAME>CONSTRUCTOR</CMPNAME>
<LANGU>E</LANGU>
<DESCRIPT>CONSTRUCTOR</DESCRIPT>
</SEOCOMPOTX>
</DESCRIPTIONS>
</asx:values>
</asx:abap>
</abapGit>

View File

@ -0,0 +1,39 @@
class ZCX_ABAPGIT_2FA_DEL_FAILED definition
public
inheriting from ZCX_ABAPGIT_2FA_ERROR
final
create public .
public section.
methods CONSTRUCTOR
importing
!TEXTID like TEXTID optional
!PREVIOUS like PREVIOUS optional
!MV_TEXT type STRING optional .
protected section.
methods GET_DEFAULT_TEXT
redefinition .
private section.
ENDCLASS.
CLASS ZCX_ABAPGIT_2FA_DEL_FAILED IMPLEMENTATION.
method CONSTRUCTOR.
CALL METHOD SUPER->CONSTRUCTOR
EXPORTING
TEXTID = TEXTID
PREVIOUS = PREVIOUS
MV_TEXT = MV_TEXT
.
endmethod.
METHOD get_default_text.
rv_text = 'Deleting previous access tokens failed.' ##NO_TEXT.
ENDMETHOD.
ENDCLASS.

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<abapGit version="v1.0.0" serializer="LCL_OBJECT_CLAS" serializer_version="v1.0.0">
<asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0">
<asx:values>
<VSEOCLASS>
<CLSNAME>ZCX_ABAPGIT_2FA_DEL_FAILED</CLSNAME>
<VERSION>1</VERSION>
<LANGU>E</LANGU>
<DESCRIPT>del failed</DESCRIPT>
<CATEGORY>40</CATEGORY>
<EXPOSURE>2</EXPOSURE>
<STATE>1</STATE>
<CLSFINAL>X</CLSFINAL>
<CLSCCINCL>X</CLSCCINCL>
<FIXPT>X</FIXPT>
<UNICODE>X</UNICODE>
</VSEOCLASS>
<DESCRIPTIONS>
<SEOCOMPOTX>
<CLSNAME>ZCX_ABAPGIT_2FA_DEL_FAILED</CLSNAME>
<CMPNAME>CONSTRUCTOR</CMPNAME>
<LANGU>E</LANGU>
<DESCRIPT>CONSTRUCTOR</DESCRIPT>
</SEOCOMPOTX>
</DESCRIPTIONS>
</asx:values>
</asx:abap>
</abapGit>

View File

@ -0,0 +1,59 @@
class ZCX_ABAPGIT_2FA_ERROR definition
public
inheriting from CX_STATIC_CHECK
create public .
public section.
data MV_TEXT type STRING read-only .
methods CONSTRUCTOR
importing
!TEXTID like TEXTID optional
!PREVIOUS like PREVIOUS optional
!MV_TEXT type STRING optional .
methods IF_MESSAGE~GET_TEXT
redefinition .
protected section.
methods GET_DEFAULT_TEXT
returning
value(RV_TEXT) type STRING .
private section.
ENDCLASS.
CLASS ZCX_ABAPGIT_2FA_ERROR IMPLEMENTATION.
method CONSTRUCTOR.
CALL METHOD SUPER->CONSTRUCTOR
EXPORTING
TEXTID = TEXTID
PREVIOUS = PREVIOUS
.
me->MV_TEXT = MV_TEXT .
endmethod.
METHOD get_default_text.
rv_text = 'Error in two factor authentication.' ##NO_TEXT.
ENDMETHOD.
METHOD if_message~get_text.
IF mv_text IS NOT INITIAL.
result = mv_text.
ELSEIF get_default_text( ) IS NOT INITIAL.
result = get_default_text( ).
ELSE.
result = super->get_text( ).
ENDIF.
ENDMETHOD.
ENDCLASS.

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<abapGit version="v1.0.0" serializer="LCL_OBJECT_CLAS" serializer_version="v1.0.0">
<asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0">
<asx:values>
<VSEOCLASS>
<CLSNAME>ZCX_ABAPGIT_2FA_ERROR</CLSNAME>
<VERSION>1</VERSION>
<LANGU>E</LANGU>
<DESCRIPT>Error</DESCRIPT>
<CATEGORY>40</CATEGORY>
<EXPOSURE>2</EXPOSURE>
<STATE>1</STATE>
<CLSCCINCL>X</CLSCCINCL>
<FIXPT>X</FIXPT>
<UNICODE>X</UNICODE>
</VSEOCLASS>
<DESCRIPTIONS>
<SEOCOMPOTX>
<CLSNAME>ZCX_ABAPGIT_2FA_ERROR</CLSNAME>
<CMPNAME>CONSTRUCTOR</CMPNAME>
<LANGU>E</LANGU>
<DESCRIPT>CONSTRUCTOR</DESCRIPT>
</SEOCOMPOTX>
</DESCRIPTIONS>
</asx:values>
</asx:abap>
</abapGit>

View File

@ -0,0 +1,39 @@
class ZCX_ABAPGIT_2FA_GEN_FAILED definition
public
inheriting from ZCX_ABAPGIT_2FA_ERROR
final
create public .
public section.
methods CONSTRUCTOR
importing
!TEXTID like TEXTID optional
!PREVIOUS like PREVIOUS optional
!MV_TEXT type STRING optional .
protected section.
methods GET_DEFAULT_TEXT
redefinition .
private section.
ENDCLASS.
CLASS ZCX_ABAPGIT_2FA_GEN_FAILED IMPLEMENTATION.
method CONSTRUCTOR.
CALL METHOD SUPER->CONSTRUCTOR
EXPORTING
TEXTID = TEXTID
PREVIOUS = PREVIOUS
MV_TEXT = MV_TEXT
.
endmethod.
METHOD get_default_text.
rv_text = 'Two factor access token generation failed.' ##NO_TEXT.
ENDMETHOD.
ENDCLASS.

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<abapGit version="v1.0.0" serializer="LCL_OBJECT_CLAS" serializer_version="v1.0.0">
<asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0">
<asx:values>
<VSEOCLASS>
<CLSNAME>ZCX_ABAPGIT_2FA_GEN_FAILED</CLSNAME>
<VERSION>1</VERSION>
<LANGU>E</LANGU>
<DESCRIPT>gen failed</DESCRIPT>
<CATEGORY>40</CATEGORY>
<EXPOSURE>2</EXPOSURE>
<STATE>1</STATE>
<CLSFINAL>X</CLSFINAL>
<CLSCCINCL>X</CLSCCINCL>
<FIXPT>X</FIXPT>
<UNICODE>X</UNICODE>
</VSEOCLASS>
<DESCRIPTIONS>
<SEOCOMPOTX>
<CLSNAME>ZCX_ABAPGIT_2FA_GEN_FAILED</CLSNAME>
<CMPNAME>CONSTRUCTOR</CMPNAME>
<LANGU>E</LANGU>
<DESCRIPT>CONSTRUCTOR</DESCRIPT>
</SEOCOMPOTX>
</DESCRIPTIONS>
</asx:values>
</asx:abap>
</abapGit>

View File

@ -0,0 +1,39 @@
class ZCX_ABAPGIT_2FA_ILLEGAL_STATE definition
public
inheriting from ZCX_ABAPGIT_2FA_ERROR
final
create public .
public section.
methods CONSTRUCTOR
importing
!TEXTID like TEXTID optional
!PREVIOUS like PREVIOUS optional
!MV_TEXT type STRING optional .
protected section.
methods GET_DEFAULT_TEXT
redefinition .
private section.
ENDCLASS.
CLASS ZCX_ABAPGIT_2FA_ILLEGAL_STATE IMPLEMENTATION.
method CONSTRUCTOR.
CALL METHOD SUPER->CONSTRUCTOR
EXPORTING
TEXTID = TEXTID
PREVIOUS = PREVIOUS
MV_TEXT = MV_TEXT
.
endmethod.
METHOD get_default_text.
rv_text = 'Illegal state.' ##NO_TEXT.
ENDMETHOD.
ENDCLASS.

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<abapGit version="v1.0.0" serializer="LCL_OBJECT_CLAS" serializer_version="v1.0.0">
<asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0">
<asx:values>
<VSEOCLASS>
<CLSNAME>ZCX_ABAPGIT_2FA_ILLEGAL_STATE</CLSNAME>
<VERSION>1</VERSION>
<LANGU>E</LANGU>
<DESCRIPT>illegal state</DESCRIPT>
<CATEGORY>40</CATEGORY>
<EXPOSURE>2</EXPOSURE>
<STATE>1</STATE>
<CLSFINAL>X</CLSFINAL>
<CLSCCINCL>X</CLSCCINCL>
<FIXPT>X</FIXPT>
<UNICODE>X</UNICODE>
</VSEOCLASS>
<DESCRIPTIONS>
<SEOCOMPOTX>
<CLSNAME>ZCX_ABAPGIT_2FA_ILLEGAL_STATE</CLSNAME>
<CMPNAME>CONSTRUCTOR</CMPNAME>
<LANGU>E</LANGU>
<DESCRIPT>CONSTRUCTOR</DESCRIPT>
</SEOCOMPOTX>
</DESCRIPTIONS>
</asx:values>
</asx:abap>
</abapGit>

View File

@ -0,0 +1,39 @@
class ZCX_ABAPGIT_2FA_UNSUPPORTED definition
public
inheriting from ZCX_ABAPGIT_2FA_ERROR
final
create public .
public section.
methods CONSTRUCTOR
importing
!TEXTID like TEXTID optional
!PREVIOUS like PREVIOUS optional
!MV_TEXT type STRING optional .
protected section.
methods GET_DEFAULT_TEXT
redefinition .
private section.
ENDCLASS.
CLASS ZCX_ABAPGIT_2FA_UNSUPPORTED IMPLEMENTATION.
method CONSTRUCTOR.
CALL METHOD SUPER->CONSTRUCTOR
EXPORTING
TEXTID = TEXTID
PREVIOUS = PREVIOUS
MV_TEXT = MV_TEXT
.
endmethod.
METHOD get_default_text.
rv_text = 'The service is not supported for two factor authentication.' ##NO_TEXT.
ENDMETHOD.
ENDCLASS.

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<abapGit version="v1.0.0" serializer="LCL_OBJECT_CLAS" serializer_version="v1.0.0">
<asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0">
<asx:values>
<VSEOCLASS>
<CLSNAME>ZCX_ABAPGIT_2FA_UNSUPPORTED</CLSNAME>
<VERSION>1</VERSION>
<LANGU>E</LANGU>
<DESCRIPT>Unsupported</DESCRIPT>
<CATEGORY>40</CATEGORY>
<EXPOSURE>2</EXPOSURE>
<STATE>1</STATE>
<CLSFINAL>X</CLSFINAL>
<CLSCCINCL>X</CLSCCINCL>
<FIXPT>X</FIXPT>
<UNICODE>X</UNICODE>
</VSEOCLASS>
<DESCRIPTIONS>
<SEOCOMPOTX>
<CLSNAME>ZCX_ABAPGIT_2FA_UNSUPPORTED</CLSNAME>
<CMPNAME>CONSTRUCTOR</CMPNAME>
<LANGU>E</LANGU>
<DESCRIPT>CONSTRUCTOR</DESCRIPT>
</SEOCOMPOTX>
</DESCRIPTIONS>
</asx:values>
</asx:abap>
</abapGit>

View File

@ -0,0 +1,79 @@
"! Defines a two factor authentication authenticator
"! <p>
"! Authenticators support one or multiple services and are able to generate access tokens using the
"! service's API using the users username, password and two factor authentication token
"! (app/sms/tokengenerator). With these access tokens the user can be authenticated to the service's
"! implementation of the git http api, just like the "normal" password would.
"! </p>
"! <p>
"! <em>LCL_2FA_AUTHENTICATOR_REGISTRY</em> can be used to find a suitable implementation for a given
"! repository.
"! </p>
"! <p>
"! Using the <em>begin</em> and <em>end</em> methods an internal session can be started and
"! completed in which internal state necessary for multiple methods will be cached. This can be
"! used to avoid having multiple http sessions between <em>authenticate</em> and
"! <em>delete_access_tokens</em>.
"! </p>
INTERFACE zif_abapgit_2fa_authenticator PUBLIC.
METHODS:
"! Generate an access token
"! @parameter iv_url | Repository url
"! @parameter iv_username | Username
"! @parameter iv_password | Password
"! @parameter iv_2fa_token | Two factor token
"! @parameter rv_access_token | Generated access token
"! @raising lcx_2fa_auth_failed | Authentication failed
"! @raising lcx_2fa_token_gen_failed | Token generation failed
authenticate IMPORTING iv_url TYPE string
iv_username TYPE string
iv_password TYPE string
iv_2fa_token TYPE string
RETURNING VALUE(rv_access_token) TYPE string
RAISING zcx_abapgit_2fa_auth_failed
zcx_abapgit_2fa_gen_failed
zcx_abapgit_2fa_comm_error,
"! Check if this authenticator instance supports the give repository url
"! @parameter iv_url | Repository url
"! @parameter rv_supported | Is supported
supports_url IMPORTING iv_url TYPE string
RETURNING VALUE(rv_supported) TYPE abap_bool,
"! Get a unique identifier for the service that hosts the repository
"! @parameter iv_url | Repository url
"! @parameter rv_id | Service id
"! @raising lcx_2fa_unsupported | Url is not supported
get_service_id_from_url IMPORTING iv_url TYPE string
RETURNING VALUE(rv_id) TYPE string
RAISING zcx_abapgit_2fa_unsupported,
"! Check if two factor authentication is required
"! @parameter iv_url | Repository url
"! @parameter iv_username | Username
"! @parameter iv_password | Password
"! @parameter rv_required | 2FA is required
is_2fa_required IMPORTING iv_url TYPE string
iv_username TYPE string
iv_password TYPE string
RETURNING VALUE(rv_required) TYPE abap_bool
RAISING zcx_abapgit_2fa_comm_error,
"! Delete all previously created access tokens for abapGit
"! @parameter iv_url | Repository url
"! @parameter iv_username | Username
"! @parameter iv_password | Password
"! @parameter iv_2fa_token | Two factor token
"! @raising lcx_2fa_token_del_failed | Token deletion failed
"! @raising lcx_2fa_auth_failed | Authentication failed
delete_access_tokens IMPORTING iv_url TYPE string
iv_username TYPE string
iv_password TYPE string
iv_2fa_token TYPE string
RAISING zcx_abapgit_2fa_del_failed
zcx_abapgit_2fa_comm_error
zcx_abapgit_2fa_auth_failed,
"! Begin an authenticator session that uses internal caching for authorizations
"! @raising lcx_2fa_illegal_state | Session already started
begin RAISING zcx_abapgit_2fa_illegal_state,
"! End an authenticator session and clear internal caches
"! @raising lcx_2fa_illegal_state | Session not running
end RAISING zcx_abapgit_2fa_illegal_state.
ENDINTERFACE.

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<abapGit version="v1.0.0" serializer="LCL_OBJECT_INTF" serializer_version="v1.0.0">
<asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0">
<asx:values>
<VSEOINTERF>
<CLSNAME>ZIF_ABAPGIT_2FA_AUTHENTICATOR</CLSNAME>
<VERSION>1</VERSION>
<LANGU>E</LANGU>
<DESCRIPT>2FA authenticator</DESCRIPT>
<EXPOSURE>2</EXPOSURE>
<STATE>1</STATE>
<UNICODE>X</UNICODE>
</VSEOINTERF>
</asx:values>
</asx:abap>
</abapGit>

View File

@ -2,800 +2,4 @@
*& Include ZABAPGIT_2FA
*&---------------------------------------------------------------------*
"! Exception base class for two factor authentication related errors
CLASS lcx_2fa_error DEFINITION INHERITING FROM cx_static_check.
PUBLIC SECTION.
METHODS:
constructor IMPORTING is_textid LIKE textid OPTIONAL
ix_previous LIKE previous OPTIONAL
iv_error_text TYPE csequence OPTIONAL,
get_text REDEFINITION.
DATA:
mv_text TYPE string READ-ONLY.
PROTECTED SECTION.
METHODS:
get_default_text RETURNING VALUE(rv_text) TYPE string.
ENDCLASS.
CLASS lcx_2fa_error IMPLEMENTATION.
METHOD constructor.
super->constructor( textid = is_textid previous = ix_previous ).
mv_text = iv_error_text.
ENDMETHOD.
METHOD get_text.
IF mv_text IS NOT INITIAL.
result = mv_text.
ELSEIF get_default_text( ) IS NOT INITIAL.
result = get_default_text( ).
ELSE.
result = super->get_text( ).
ENDIF.
ENDMETHOD.
METHOD get_default_text.
rv_text = 'Error in two factor authentication.' ##NO_TEXT.
ENDMETHOD.
ENDCLASS.
CLASS lcx_2fa_auth_failed DEFINITION INHERITING FROM lcx_2fa_error FINAL.
PROTECTED SECTION.
METHODS:
get_default_text REDEFINITION.
ENDCLASS.
CLASS lcx_2fa_auth_failed IMPLEMENTATION.
METHOD get_default_text.
rv_text = 'Authentication failed using 2FA.' ##NO_TEXT.
ENDMETHOD.
ENDCLASS.
CLASS lcx_2fa_gen_failed DEFINITION INHERITING FROM lcx_2fa_error FINAL.
PROTECTED SECTION.
METHODS:
get_default_text REDEFINITION.
ENDCLASS.
CLASS lcx_2fa_gen_failed IMPLEMENTATION.
METHOD get_default_text.
rv_text = 'Two factor access token generation failed.' ##NO_TEXT.
ENDMETHOD.
ENDCLASS.
CLASS lcx_2fa_unsupported DEFINITION INHERITING FROM lcx_2fa_error FINAL.
PROTECTED SECTION.
METHODS:
get_default_text REDEFINITION.
ENDCLASS.
CLASS lcx_2fa_unsupported IMPLEMENTATION.
METHOD get_default_text.
rv_text = 'The service is not supported for two factor authentication.' ##NO_TEXT.
ENDMETHOD.
ENDCLASS.
CLASS lcx_2fa_del_failed DEFINITION INHERITING FROM lcx_2fa_error FINAL.
PROTECTED SECTION.
METHODS:
get_default_text REDEFINITION.
ENDCLASS.
CLASS lcx_2fa_del_failed IMPLEMENTATION.
METHOD get_default_text.
rv_text = 'Deleting previous access tokens failed.' ##NO_TEXT.
ENDMETHOD.
ENDCLASS.
CLASS lcx_2fa_comm_error DEFINITION INHERITING FROM lcx_2fa_error FINAL.
PROTECTED SECTION.
METHODS:
get_default_text REDEFINITION.
ENDCLASS.
CLASS lcx_2fa_comm_error IMPLEMENTATION.
METHOD get_default_text.
rv_text = 'Communication error.' ##NO_TEXT.
ENDMETHOD.
ENDCLASS.
CLASS lcx_2fa_illegal_state DEFINITION INHERITING FROM lcx_2fa_error FINAL.
PROTECTED SECTION.
METHODS:
get_default_text REDEFINITION.
ENDCLASS.
CLASS lcx_2fa_illegal_state IMPLEMENTATION.
METHOD get_default_text.
rv_text = 'Illegal state.' ##NO_TEXT.
ENDMETHOD.
ENDCLASS.
"! Defines a two factor authentication authenticator
"! <p>
"! Authenticators support one or multiple services and are able to generate access tokens using the
"! service's API using the users username, password and two factor authentication token
"! (app/sms/tokengenerator). With these access tokens the user can be authenticated to the service's
"! implementation of the git http api, just like the "normal" password would.
"! </p>
"! <p>
"! <em>LCL_2FA_AUTHENTICATOR_REGISTRY</em> can be used to find a suitable implementation for a given
"! repository.
"! </p>
"! <p>
"! Using the <em>begin</em> and <em>end</em> methods an internal session can be started and
"! completed in which internal state necessary for multiple methods will be cached. This can be
"! used to avoid having multiple http sessions between <em>authenticate</em> and
"! <em>delete_access_tokens</em>.
"! </p>
INTERFACE lif_2fa_authenticator.
METHODS:
"! Generate an access token
"! @parameter iv_url | Repository url
"! @parameter iv_username | Username
"! @parameter iv_password | Password
"! @parameter iv_2fa_token | Two factor token
"! @parameter rv_access_token | Generated access token
"! @raising lcx_2fa_auth_failed | Authentication failed
"! @raising lcx_2fa_token_gen_failed | Token generation failed
authenticate IMPORTING iv_url TYPE string
iv_username TYPE string
iv_password TYPE string
iv_2fa_token TYPE string
RETURNING VALUE(rv_access_token) TYPE string
RAISING lcx_2fa_auth_failed
lcx_2fa_gen_failed
lcx_2fa_comm_error,
"! Check if this authenticator instance supports the give repository url
"! @parameter iv_url | Repository url
"! @parameter rv_supported | Is supported
supports_url IMPORTING iv_url TYPE string
RETURNING VALUE(rv_supported) TYPE abap_bool,
"! Get a unique identifier for the service that hosts the repository
"! @parameter iv_url | Repository url
"! @parameter rv_id | Service id
"! @raising lcx_2fa_unsupported | Url is not supported
get_service_id_from_url IMPORTING iv_url TYPE string
RETURNING VALUE(rv_id) TYPE string
RAISING lcx_2fa_unsupported,
"! Check if two factor authentication is required
"! @parameter iv_url | Repository url
"! @parameter iv_username | Username
"! @parameter iv_password | Password
"! @parameter rv_required | 2FA is required
is_2fa_required IMPORTING iv_url TYPE string
iv_username TYPE string
iv_password TYPE string
RETURNING VALUE(rv_required) TYPE abap_bool
RAISING lcx_2fa_comm_error,
"! Delete all previously created access tokens for abapGit
"! @parameter iv_url | Repository url
"! @parameter iv_username | Username
"! @parameter iv_password | Password
"! @parameter iv_2fa_token | Two factor token
"! @raising lcx_2fa_token_del_failed | Token deletion failed
"! @raising lcx_2fa_auth_failed | Authentication failed
delete_access_tokens IMPORTING iv_url TYPE string
iv_username TYPE string
iv_password TYPE string
iv_2fa_token TYPE string
RAISING lcx_2fa_del_failed
lcx_2fa_comm_error
lcx_2fa_auth_failed,
"! Begin an authenticator session that uses internal caching for authorizations
"! @raising lcx_2fa_illegal_state | Session already started
begin RAISING lcx_2fa_illegal_state,
"! End an authenticator session and clear internal caches
"! @raising lcx_2fa_illegal_state | Session not running
end RAISING lcx_2fa_illegal_state.
ENDINTERFACE.
"! Default <em>LIF_2FA-AUTHENTICATOR</em> implememtation
CLASS lcl_2fa_auth_base DEFINITION
ABSTRACT
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES:
lif_2fa_authenticator.
ALIASES:
authenticate FOR lif_2fa_authenticator~authenticate,
supports_url FOR lif_2fa_authenticator~supports_url,
get_service_id_from_url FOR lif_2fa_authenticator~get_service_id_from_url,
is_2fa_required FOR lif_2fa_authenticator~is_2fa_required,
delete_access_tokens FOR lif_2fa_authenticator~delete_access_tokens,
begin FOR lif_2fa_authenticator~begin,
end FOR lif_2fa_authenticator~end.
METHODS:
"! @parameter iv_supported_url_regex | Regular expression to check if a repository url is
"! supported, used for default implementation of
"! <em>SUPPORTS_URL</em>
constructor IMPORTING iv_supported_url_regex TYPE clike.
PROTECTED SECTION.
CLASS-METHODS:
"! Helper method to raise class based exception after traditional exception was raised
"! <p>
"! <em>sy-msg...</em> must be set right before calling!
"! </p>
raise_comm_error_from_sy RAISING lcx_2fa_comm_error.
METHODS:
"! @parameter rv_running | Internal session is currently active
is_session_running RETURNING VALUE(rv_running) TYPE abap_bool.
PRIVATE SECTION.
DATA:
mo_url_regex TYPE REF TO cl_abap_regex,
mv_session_running TYPE abap_bool.
ENDCLASS.
CLASS lcl_2fa_auth_base IMPLEMENTATION.
METHOD constructor.
CREATE OBJECT mo_url_regex
EXPORTING
pattern = iv_supported_url_regex
ignore_case = abap_true.
ENDMETHOD.
METHOD authenticate.
RAISE EXCEPTION TYPE lcx_2fa_auth_failed. " Needs to be overwritten in subclasses
ENDMETHOD.
METHOD supports_url.
rv_supported = mo_url_regex->create_matcher( text = iv_url )->match( ).
ENDMETHOD.
METHOD get_service_id_from_url.
rv_id = 'UNKNOWN SERVICE'. " Please overwrite in subclasses
ENDMETHOD.
METHOD is_2fa_required.
rv_required = abap_false.
ENDMETHOD.
METHOD delete_access_tokens.
RAISE EXCEPTION TYPE lcx_2fa_del_failed. " Needs to be overwritten in subclasses
ENDMETHOD.
METHOD raise_comm_error_from_sy.
DATA: lv_error_msg TYPE string.
MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno
WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4
INTO lv_error_msg.
RAISE EXCEPTION TYPE lcx_2fa_comm_error
EXPORTING
iv_error_text = |Communication error: { lv_error_msg }| ##NO_TEXT.
ENDMETHOD.
METHOD begin.
IF mv_session_running = abap_true.
RAISE EXCEPTION TYPE lcx_2fa_illegal_state.
ENDIF.
mv_session_running = abap_true.
ENDMETHOD.
METHOD end.
IF mv_session_running = abap_false.
RAISE EXCEPTION TYPE lcx_2fa_illegal_state.
ENDIF.
mv_session_running = abap_false.
ENDMETHOD.
METHOD is_session_running.
rv_running = mv_session_running.
ENDMETHOD.
ENDCLASS.
CLASS lcl_2fa_github_auth DEFINITION INHERITING FROM lcl_2fa_auth_base
FINAL CREATE PUBLIC.
PUBLIC SECTION.
METHODS:
constructor,
get_service_id_from_url REDEFINITION,
authenticate REDEFINITION,
is_2fa_required REDEFINITION,
delete_access_tokens REDEFINITION,
end REDEFINITION.
PROTECTED SECTION.
PRIVATE SECTION.
CONSTANTS:
gc_github_api_url TYPE string VALUE `https://api.github.com/`,
gc_otp_header_name TYPE string VALUE `X-Github-OTP`,
gc_restendpoint_authorizations TYPE string VALUE `/authorizations`.
CLASS-METHODS:
set_new_token_request IMPORTING ii_request TYPE REF TO if_http_request,
get_token_from_response IMPORTING ii_response TYPE REF TO if_http_response
RETURNING VALUE(rv_token) TYPE string,
set_list_token_request IMPORTING ii_request TYPE REF TO if_http_request,
get_tobedel_tokens_from_resp IMPORTING ii_response TYPE REF TO if_http_response
RETURNING VALUE(rt_ids) TYPE stringtab,
set_del_token_request IMPORTING ii_request TYPE REF TO if_http_request
iv_token_id TYPE string.
METHODS:
get_authenticated_client IMPORTING iv_username TYPE string
iv_password TYPE string
iv_2fa_token TYPE string
RETURNING VALUE(ri_client) TYPE REF TO if_http_client
RAISING lcx_2fa_auth_failed
lcx_2fa_comm_error.
DATA:
mi_authenticated_session TYPE REF TO if_http_client.
ENDCLASS.
CLASS lcl_2fa_github_auth IMPLEMENTATION.
METHOD constructor.
super->constructor( '^https?://(www\.)?github.com.*$' ).
ENDMETHOD.
METHOD authenticate.
DATA: li_http_client TYPE REF TO if_http_client,
lv_http_code TYPE i,
lv_http_code_description TYPE string.
" 1. Try to login to GitHub API
li_http_client = get_authenticated_client( iv_username = iv_username
iv_password = iv_password
iv_2fa_token = iv_2fa_token ).
" 2. Create an access token which can be used instead of a password
" https://developer.github.com/v3/oauth_authorizations/#create-a-new-authorization
set_new_token_request( ii_request = li_http_client->request ).
li_http_client->send( EXCEPTIONS OTHERS = 1 ).
IF sy-subrc <> 0.
raise_comm_error_from_sy( ).
ENDIF.
li_http_client->receive( EXCEPTIONS OTHERS = 1 ).
IF sy-subrc <> 0.
raise_comm_error_from_sy( ).
ENDIF.
li_http_client->response->get_status(
IMPORTING
code = lv_http_code
reason = lv_http_code_description ).
IF lv_http_code <> 201.
RAISE EXCEPTION TYPE lcx_2fa_gen_failed
EXPORTING
iv_error_text = |Token generation failed: { lv_http_code } { lv_http_code_description }|.
ENDIF.
rv_access_token = get_token_from_response( li_http_client->response ).
IF rv_access_token IS INITIAL.
RAISE EXCEPTION TYPE lcx_2fa_gen_failed
EXPORTING
iv_error_text = 'Token generation failed: parser error' ##NO_TEXT.
ENDIF.
" GitHub might need some time until the new token is ready to use, give it a second
CALL FUNCTION 'RZL_SLEEP'.
ENDMETHOD.
METHOD set_new_token_request.
DATA: lv_json_string TYPE string.
lv_json_string = `{"scopes":["repo"],"note":"Generated by abapGit","fingerprint":"abapGit2FA"}`.
ii_request->set_data( cl_abap_codepage=>convert_to( lv_json_string ) ).
ii_request->set_header_field( name = if_http_header_fields_sap=>request_uri
value = gc_restendpoint_authorizations ).
ii_request->set_method( if_http_request=>co_request_method_post ).
ENDMETHOD.
METHOD set_list_token_request.
ii_request->set_header_field( name = if_http_header_fields_sap=>request_uri
value = gc_restendpoint_authorizations ).
ii_request->set_method( if_http_request=>co_request_method_get ).
ENDMETHOD.
METHOD set_del_token_request.
DATA: lv_url TYPE string.
lv_url = |{ gc_restendpoint_authorizations }/{ iv_token_id }|.
ii_request->set_header_field( name = if_http_header_fields_sap=>request_uri
value = lv_url ).
" Other methods than POST and GET do not have constants unfortunately
" ii_request->set_method( if_http_request=>co_request_method_delete ).
ii_request->set_method( 'DELETE' ).
ENDMETHOD.
METHOD get_token_from_response.
CONSTANTS: lc_search_regex TYPE string VALUE `.*"token":"([^"]*).*$`.
DATA: lv_response TYPE string,
lo_regex TYPE REF TO cl_abap_regex,
lo_matcher TYPE REF TO cl_abap_matcher.
lv_response = cl_abap_codepage=>convert_from( ii_response->get_data( ) ).
CREATE OBJECT lo_regex
EXPORTING
pattern = lc_search_regex.
lo_matcher = lo_regex->create_matcher( text = lv_response ).
IF lo_matcher->match( ) = abap_true.
rv_token = lo_matcher->get_submatch( 1 ).
ENDIF.
ENDMETHOD.
METHOD get_tobedel_tokens_from_resp.
CONSTANTS: lc_search_regex TYPE string
VALUE `\{"id": ?(\d+)[^\{]*"app":\{[^\{^\}]*\}[^\{]*"fingerprint": ?` &
`"abapGit2FA"[^\{]*\}`.
DATA: lv_response TYPE string,
lo_regex TYPE REF TO cl_abap_regex,
lo_matcher TYPE REF TO cl_abap_matcher.
lv_response = cl_abap_codepage=>convert_from( ii_response->get_data( ) ).
CREATE OBJECT lo_regex
EXPORTING
pattern = lc_search_regex.
lo_matcher = lo_regex->create_matcher( text = lv_response ).
WHILE lo_matcher->find_next( ) = abap_true.
APPEND lo_matcher->get_submatch( 1 ) TO rt_ids.
ENDWHILE.
ENDMETHOD.
METHOD get_service_id_from_url.
rv_id = 'github'.
ENDMETHOD.
METHOD is_2fa_required.
DATA: li_client TYPE REF TO if_http_client,
lo_proxy TYPE REF TO zcl_abapgit_proxy_config.
CREATE OBJECT lo_proxy.
cl_http_client=>create_by_url(
EXPORTING
url = gc_github_api_url
ssl_id = 'ANONYM'
proxy_host = lo_proxy->get_proxy_url( )
proxy_service = lo_proxy->get_proxy_port( )
IMPORTING
client = li_client
EXCEPTIONS
argument_not_found = 1
plugin_not_active = 2
internal_error = 3
OTHERS = 4 ).
IF sy-subrc <> 0.
raise_comm_error_from_sy( ).
ENDIF.
li_client->propertytype_logon_popup = if_http_client=>co_disabled.
" The request needs to use something other than GET and it needs to be send to an endpoint
" to trigger a SMS.
li_client->request->set_header_field( name = if_http_header_fields_sap=>request_uri
value = gc_restendpoint_authorizations ).
li_client->request->set_method( if_http_request=>co_request_method_post ).
" 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( EXCEPTIONS OTHERS = 1 ).
IF sy-subrc <> 0.
raise_comm_error_from_sy( ).
ENDIF.
li_client->receive( EXCEPTIONS OTHERS = 1 ).
IF sy-subrc <> 0.
raise_comm_error_from_sy( ).
ENDIF.
" The response will either be UNAUTHORIZED or MALFORMED which is both fine.
IF li_client->response->get_header_field( gc_otp_header_name ) CP 'required*'.
rv_required = abap_true.
ENDIF.
ENDMETHOD.
METHOD delete_access_tokens.
DATA: li_http_client TYPE REF TO if_http_client,
lv_http_code TYPE i,
lv_http_code_description TYPE string,
lt_tobedeleted_tokens TYPE stringtab.
FIELD-SYMBOLS: <lv_id> TYPE string.
li_http_client = get_authenticated_client( iv_username = iv_username
iv_password = iv_password
iv_2fa_token = iv_2fa_token ).
set_list_token_request( li_http_client->request ).
li_http_client->send( EXCEPTIONS OTHERS = 1 ).
IF sy-subrc <> 0.
raise_comm_error_from_sy( ).
ENDIF.
li_http_client->receive( EXCEPTIONS OTHERS = 1 ).
IF sy-subrc <> 0.
raise_comm_error_from_sy( ).
ENDIF.
li_http_client->response->get_status(
IMPORTING
code = lv_http_code
reason = lv_http_code_description ).
IF lv_http_code <> 200.
RAISE EXCEPTION TYPE lcx_2fa_del_failed
EXPORTING
iv_error_text = |Could not fetch current 2FA authorizations: | &&
|{ lv_http_code } { lv_http_code_description }|.
ENDIF.
lt_tobedeleted_tokens = get_tobedel_tokens_from_resp( li_http_client->response ).
LOOP AT lt_tobedeleted_tokens ASSIGNING <lv_id> WHERE table_line IS NOT INITIAL.
set_del_token_request( ii_request = li_http_client->request
iv_token_id = <lv_id> ).
li_http_client->send( EXCEPTIONS OTHERS = 1 ).
IF sy-subrc <> 0.
raise_comm_error_from_sy( ).
ENDIF.
li_http_client->receive( EXCEPTIONS OTHERS = 1 ).
IF sy-subrc <> 0.
raise_comm_error_from_sy( ).
ENDIF.
li_http_client->response->get_status(
IMPORTING
code = lv_http_code
reason = lv_http_code_description ).
IF lv_http_code <> 204.
RAISE EXCEPTION TYPE lcx_2fa_del_failed
EXPORTING
iv_error_text = |Could not delete token '{ <lv_id> }': | &&
|{ lv_http_code } { lv_http_code_description }|.
ENDIF.
ENDLOOP.
ENDMETHOD.
METHOD get_authenticated_client.
DATA: lv_http_code TYPE i,
lv_http_code_description TYPE string,
lo_settings TYPE REF TO zcl_abapgit_settings.
" If there is a cached client return it instead
IF is_session_running( ) = abap_true AND mi_authenticated_session IS BOUND.
ri_client = mi_authenticated_session.
RETURN.
ENDIF.
" Try to login to GitHub API with username, password and 2fa token
lo_settings = zcl_abapgit_persist_settings=>get_instance( )->read( ).
cl_http_client=>create_by_url(
EXPORTING
url = gc_github_api_url
ssl_id = 'ANONYM'
proxy_host = lo_settings->get_proxy_url( )
proxy_service = lo_settings->get_proxy_port( )
IMPORTING
client = ri_client
EXCEPTIONS
argument_not_found = 1
plugin_not_active = 2
internal_error = 3
OTHERS = 4 ).
IF sy-subrc <> 0.
raise_comm_error_from_sy( ).
ENDIF.
" https://developer.github.com/v3/auth/#working-with-two-factor-authentication
ri_client->propertytype_accept_cookie = if_http_client=>co_enabled.
ri_client->request->set_header_field( name = gc_otp_header_name value = iv_2fa_token ).
ri_client->authenticate( username = iv_username password = iv_password ).
ri_client->propertytype_logon_popup = if_http_client=>co_disabled.
ri_client->send( EXCEPTIONS OTHERS = 1 ).
IF sy-subrc <> 0.
raise_comm_error_from_sy( ).
ENDIF.
ri_client->receive( EXCEPTIONS OTHERS = 1 ).
IF sy-subrc <> 0.
raise_comm_error_from_sy( ).
ENDIF.
" Check if authentication has succeeded
ri_client->response->get_status(
IMPORTING
code = lv_http_code
reason = lv_http_code_description ).
IF lv_http_code <> 200.
RAISE EXCEPTION TYPE lcx_2fa_auth_failed
EXPORTING
iv_error_text = |Authentication failed: { lv_http_code_description }|.
ENDIF.
" Cache the authenticated http session / client to avoid unnecessary additional authentication
IF is_session_running( ) = abap_true.
mi_authenticated_session = ri_client.
ENDIF.
ENDMETHOD.
METHOD end.
super->end( ).
FREE mi_authenticated_session.
ENDMETHOD.
ENDCLASS.
"! Static registry class to find <em>LIF_2FA_AUTHENTICATOR</em> instances
CLASS lcl_2fa_auth_registry DEFINITION
FINAL
CREATE PRIVATE.
PUBLIC SECTION.
CLASS-METHODS:
class_constructor,
"! Retrieve an authenticator instance by url
"! @parameter iv_url | Url of the repository / service
"! @parameter ro_authenticator | Found authenticator instance
"! @raising lcx_2fa_unsupported | No authenticator found that supports the service
get_authenticator_for_url IMPORTING iv_url TYPE string
RETURNING VALUE(ro_authenticator) TYPE REF TO lif_2fa_authenticator
RAISING lcx_2fa_unsupported,
"! Check if there is a two factor authenticator available for the url
"! @parameter iv_url | Url of the repository / service
"! @parameter rv_supported | 2FA is supported
is_url_supported IMPORTING iv_url TYPE string
RETURNING VALUE(rv_supported) TYPE abap_bool,
"! Offer to use two factor authentication if supported and required
"! <p>
"! This uses GUI functionality to display a popup to request the user to enter a two factor
"! token. Also an dummy authentication request might be used to find out if two factor
"! authentication is required for the account.
"! </p>
"! @parameter iv_url | Url of the repository / service
"! @parameter cv_username | Username
"! @parameter cv_password | Password, will be replaced by an access token if two factor
"! authentication succeeds
"! @raising zcx_abapgit_exception | Error in two factor authentication
use_2fa_if_required IMPORTING iv_url TYPE string
CHANGING cv_username TYPE string
cv_password TYPE string
RAISING zcx_abapgit_exception.
CLASS-DATA:
"! All authenticators managed by the registry
gt_registered_authenticators TYPE HASHED TABLE OF REF TO lif_2fa_authenticator
WITH UNIQUE KEY table_line READ-ONLY.
PRIVATE SECTION.
CLASS-METHODS:
popup_token
RETURNING VALUE(rv_token) TYPE string
RAISING zcx_abapgit_exception.
ENDCLASS.
CLASS lcl_2fa_auth_registry IMPLEMENTATION.
METHOD class_constructor.
DEFINE _register.
CREATE OBJECT li_authenticator TYPE &1.
INSERT li_authenticator INTO TABLE gt_registered_authenticators.
END-OF-DEFINITION.
DATA: li_authenticator TYPE REF TO lif_2fa_authenticator.
" If there are new authenticators these need to be added here manually.
" I do not think there is an equivalent to SEO_INTERFACE_IMPLEM_GET_ALL for local classes
" without invoking the compiler directly.
_register: lcl_2fa_github_auth.
ENDMETHOD.
METHOD get_authenticator_for_url.
FIELD-SYMBOLS: <lo_authenticator> LIKE LINE OF gt_registered_authenticators.
LOOP AT gt_registered_authenticators ASSIGNING <lo_authenticator>.
IF <lo_authenticator>->supports_url( iv_url ) = abap_true.
ro_authenticator = <lo_authenticator>.
RETURN.
ENDIF.
ENDLOOP.
RAISE EXCEPTION TYPE lcx_2fa_unsupported.
ENDMETHOD.
METHOD is_url_supported.
TRY.
get_authenticator_for_url( iv_url ).
rv_supported = abap_true.
CATCH lcx_2fa_unsupported ##NO_HANDLER.
ENDTRY.
ENDMETHOD.
METHOD use_2fa_if_required.
DATA: li_authenticator TYPE REF TO lif_2fa_authenticator,
lv_2fa_token TYPE string,
lv_access_token TYPE string,
lx_ex TYPE REF TO cx_root.
IF is_url_supported( iv_url ) = abap_false.
RETURN.
ENDIF.
TRY.
li_authenticator = get_authenticator_for_url( iv_url ).
li_authenticator->begin( ).
" Is two factor authentication required for this account?
IF li_authenticator->is_2fa_required( iv_url = iv_url
iv_username = cv_username
iv_password = cv_password ) = abap_true.
lv_2fa_token = popup_token( ).
" Delete an old access token if it exists
li_authenticator->delete_access_tokens( iv_url = iv_url
iv_username = cv_username
iv_password = cv_password
iv_2fa_token = lv_2fa_token ).
" Get a new access token
lv_access_token = li_authenticator->authenticate( iv_url = iv_url
iv_username = cv_username
iv_password = cv_password
iv_2fa_token = lv_2fa_token ).
" Use the access token instead of the password
cv_password = lv_access_token.
ENDIF.
li_authenticator->end( ).
CATCH lcx_2fa_error INTO lx_ex.
TRY.
li_authenticator->end( ).
CATCH lcx_2fa_illegal_state ##NO_HANDLER.
ENDTRY.
zcx_abapgit_exception=>raise( |2FA error: { lx_ex->get_text( ) }| ).
ENDTRY.
ENDMETHOD.
METHOD popup_token.
DATA: lv_returncode TYPE c,
lt_fields TYPE TABLE OF sval.
FIELD-SYMBOLS: <ls_field> LIKE LINE OF lt_fields.
APPEND INITIAL LINE TO lt_fields ASSIGNING <ls_field>.
<ls_field>-tabname = 'TADIR'.
<ls_field>-fieldname = 'OBJ_NAME'.
<ls_field>-fieldtext = 'Two factor auth. token'.
CALL FUNCTION 'POPUP_GET_VALUES'
EXPORTING
no_value_check = abap_true
popup_title = 'Two factor auth. token'
IMPORTING
returncode = lv_returncode
TABLES
fields = lt_fields
EXCEPTIONS
error_in_fields = 1
OTHERS = 2. "#EC NOTEXT
IF sy-subrc <> 0.
zcx_abapgit_exception=>raise( 'Error from POPUP_GET_VALUES' ).
ENDIF.
IF lv_returncode = 'A'.
zcx_abapgit_exception=>raise( 'Authentication cancelled' ).
ENDIF.
READ TABLE lt_fields INDEX 1 ASSIGNING <ls_field>.
ASSERT sy-subrc = 0.
rv_token = <ls_field>-value.
ENDMETHOD.
ENDCLASS.
* todo, include to be deleted

View File

@ -2,54 +2,6 @@
*& Include ZABAPGIT_HTTP
*&---------------------------------------------------------------------*
CLASS lcl_proxy_auth DEFINITION FINAL.
PUBLIC SECTION.
CLASS-METHODS:
run
IMPORTING ii_client TYPE REF TO if_http_client
RAISING zcx_abapgit_exception.
PRIVATE SECTION.
CLASS-DATA: gv_username TYPE string,
gv_password TYPE string.
CLASS-METHODS: enter RAISING zcx_abapgit_exception.
ENDCLASS.
CLASS lcl_proxy_auth IMPLEMENTATION.
METHOD run.
IF gv_username IS INITIAL OR gv_password IS INITIAL.
enter( ).
ENDIF.
ii_client->authenticate(
proxy_authentication = abap_true
username = gv_username
password = gv_password ).
ENDMETHOD.
METHOD enter.
zcl_abapgit_password_dialog=>popup(
EXPORTING
iv_repo_url = 'Proxy Authentication'
CHANGING
cv_user = gv_username
cv_pass = gv_password ).
IF gv_username IS INITIAL OR gv_password IS INITIAL.
zcx_abapgit_exception=>raise( 'Proxy auth failed' ).
ENDIF.
ENDMETHOD.
ENDCLASS.
CLASS lcl_http DEFINITION FINAL.
PUBLIC SECTION.
@ -131,7 +83,7 @@ CLASS lcl_http IMPLEMENTATION.
ENDIF.
IF lo_proxy_configuration->get_proxy_authentication( iv_url ) = abap_true.
lcl_proxy_auth=>run( li_client ).
zcl_abapgit_proxy_auth=>run( li_client ).
ENDIF.
CREATE OBJECT ro_client
@ -255,7 +207,7 @@ CLASS lcl_http IMPLEMENTATION.
ENDIF.
" Offer two factor authentication if it is available and required
lcl_2fa_auth_registry=>use_2fa_if_required(
zcl_abapgit_2fa_auth_registry=>use_2fa_if_required(
EXPORTING
iv_url = iv_url
CHANGING