mirror of
https://github.com/abapGit/abapGit.git
synced 2025-05-02 13:03:01 +08:00
Support GitHub two factor authentication
This commit is contained in:
parent
cdcbb12e9d
commit
36b817b405
|
@ -53,6 +53,7 @@ INCLUDE zabapgit_stage.
|
|||
INCLUDE zabapgit_git_helpers.
|
||||
INCLUDE zabapgit_repo.
|
||||
INCLUDE zabapgit_stage_logic.
|
||||
INCLUDE zabapgit_2fa.
|
||||
INCLUDE zabapgit_http.
|
||||
INCLUDE zabapgit_git.
|
||||
INCLUDE zabapgit_objects.
|
||||
|
|
586
src/zabapgit_2fa.prog.abap
Normal file
586
src/zabapgit_2fa.prog.abap
Normal file
|
@ -0,0 +1,586 @@
|
|||
*&---------------------------------------------------------------------*
|
||||
*& 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_token_gen_failed DEFINITION INHERITING FROM lcx_2fa_error FINAL.
|
||||
PROTECTED SECTION.
|
||||
METHODS:
|
||||
get_default_text REDEFINITION.
|
||||
ENDCLASS.
|
||||
|
||||
CLASS lcx_2fa_token_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_no_cached_token DEFINITION INHERITING FROM lcx_2fa_error FINAL.
|
||||
PROTECTED SECTION.
|
||||
METHODS:
|
||||
get_default_text REDEFINITION.
|
||||
ENDCLASS.
|
||||
|
||||
CLASS lcx_2fa_no_cached_token IMPLEMENTATION.
|
||||
METHOD get_default_text.
|
||||
rv_text = 'Cached two factor access token requested but not available.' ##NO_TEXT.
|
||||
ENDMETHOD.
|
||||
ENDCLASS.
|
||||
|
||||
CLASS lcx_2fa_cache_deletion_failed DEFINITION INHERITING FROM lcx_2fa_error FINAL.
|
||||
PROTECTED SECTION.
|
||||
METHODS:
|
||||
get_default_text REDEFINITION.
|
||||
ENDCLASS.
|
||||
|
||||
CLASS lcx_2fa_cache_deletion_failed IMPLEMENTATION.
|
||||
METHOD get_default_text.
|
||||
rv_text = 'Cache deletion failed.' ##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. The authenticator can
|
||||
"! also store and retrieve the access token it generated.
|
||||
"! </p>
|
||||
"! <p>
|
||||
"! <em>LCL_2FA_AUTHENTICATOR_REGISTRY</em> can be used to find a suitable implementation for a given
|
||||
"! repository.
|
||||
"! </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_token_gen_failed,
|
||||
"! 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
|
||||
get_service_id_from_url IMPORTING iv_url TYPE string
|
||||
RETURNING VALUE(rv_id) TYPE string,
|
||||
"! Check if there is a cached access token (for the current user)
|
||||
"! @parameter iv_url | Repository url
|
||||
"! @parameter rv_available | Token is cached
|
||||
is_cached_access_token_avail IMPORTING iv_url TYPE string
|
||||
RETURNING VALUE(rv_available) TYPE abap_bool,
|
||||
"! Get a cached access token
|
||||
"! <p>
|
||||
"! Username and password are also parameters to decrypt the token if needed. They must no
|
||||
"! necessarily be provided if the used authenticator does not use encryption.
|
||||
"! </p>
|
||||
"! @parameter iv_url | Repository url
|
||||
"! @parameter iv_username | Username
|
||||
"! @parameter iv_password | Password
|
||||
"! @parameter rv_token | Access token
|
||||
"! @raising lcx_2fa_no_cached_token | There is no cached token
|
||||
get_cached_access_token IMPORTING iv_url TYPE string
|
||||
iv_username TYPE string OPTIONAL
|
||||
iv_password TYPE string OPTIONAL
|
||||
RETURNING VALUE(rv_token) TYPE string
|
||||
RAISING lcx_2fa_no_cached_token,
|
||||
"! Delete a cached token
|
||||
"! @parameter iv_url | Repository url
|
||||
"! @raising lcx_2fa_cache_deletion_failed | Deletion failed
|
||||
delete_cached_access_token IMPORTING iv_url TYPE string
|
||||
RAISING lcx_2fa_cache_deletion_failed.
|
||||
ENDINTERFACE.
|
||||
|
||||
"! Default <em>LIF_2FA-AUTHENTICATOR</em> implememtation
|
||||
"! <p>
|
||||
"! This uses the user settings to store cached access tokens and encrypts / decrypts them as needed.
|
||||
"! </p>
|
||||
CLASS lcl_2fa_authenticator_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_cached_access_token_avail FOR lif_2fa_authenticator~is_cached_access_token_avail,
|
||||
get_cached_access_token FOR lif_2fa_authenticator~get_cached_access_token,
|
||||
delete_cached_token FOR lif_2fa_authenticator~delete_cached_access_token.
|
||||
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.
|
||||
METHODS:
|
||||
"! Subclass implementation of <em>LIF_2FA_AUTHENTICATOR=>AUTHENTICATE</em>
|
||||
"! <p>
|
||||
"! The caller will take care of caching the token.
|
||||
"! </p>
|
||||
"! @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_internal ABSTRACT 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_token_gen_failed,
|
||||
"! 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_internal_error_from_sy FINAL RAISING lcx_2fa_auth_failed.
|
||||
PRIVATE SECTION.
|
||||
DATA:
|
||||
mo_url_regex TYPE REF TO cl_abap_regex.
|
||||
ENDCLASS.
|
||||
|
||||
CLASS lcl_2fa_authenticator_base IMPLEMENTATION.
|
||||
METHOD constructor.
|
||||
CREATE OBJECT mo_url_regex
|
||||
EXPORTING
|
||||
pattern = iv_supported_url_regex
|
||||
ignore_case = abap_true.
|
||||
ENDMETHOD.
|
||||
|
||||
METHOD authenticate.
|
||||
DATA: lv_encrypted_token TYPE string.
|
||||
|
||||
rv_access_token = authenticate_internal( iv_url = iv_url
|
||||
iv_username = iv_username
|
||||
iv_password = iv_password
|
||||
iv_2fa_token = iv_2fa_token ).
|
||||
|
||||
" Store the access token, by default in the user settings
|
||||
|
||||
" 1. Encrypt it
|
||||
* lv_encrypted_token = cl_encryption_helper=>encrypt_symmetric( iv_text = rv_access_token
|
||||
* iv_key = iv_password ).
|
||||
" TODO: Find something like the above for symmetric encryption
|
||||
lv_encrypted_token = rv_access_token.
|
||||
|
||||
" 2. Store it
|
||||
TRY.
|
||||
lcl_app=>user( )->set_2fa_access_token( iv_service_id = get_service_id_from_url( iv_url )
|
||||
iv_username = iv_username
|
||||
iv_token = lv_encrypted_token ).
|
||||
CATCH lcx_exception ##NO_HANDLER.
|
||||
" Not the biggest of deals if caching the token fails
|
||||
ENDTRY.
|
||||
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_cached_access_token_avail.
|
||||
DATA: lv_service_id TYPE string.
|
||||
|
||||
lv_service_id = get_service_id_from_url( iv_url ).
|
||||
|
||||
" Default storage location is user settings
|
||||
TRY.
|
||||
rv_available = boolc( lcl_app=>user( )->get_2fa_access_token( lv_service_id )
|
||||
IS NOT INITIAL ).
|
||||
CATCH lcx_exception.
|
||||
rv_available = abap_false.
|
||||
ENDTRY.
|
||||
ENDMETHOD.
|
||||
|
||||
METHOD get_cached_access_token.
|
||||
DATA: lv_access_token_encrypted TYPE string,
|
||||
lx_error TYPE REF TO cx_root.
|
||||
|
||||
TRY.
|
||||
lv_access_token_encrypted
|
||||
= lcl_app=>user( )->get_2fa_access_token( get_service_id_from_url( iv_url ) ).
|
||||
CATCH lcx_exception INTO lx_error.
|
||||
RAISE EXCEPTION TYPE lcx_2fa_no_cached_token
|
||||
EXPORTING
|
||||
ix_previous = lx_error
|
||||
iv_error_text = lx_error->get_text( ).
|
||||
ENDTRY.
|
||||
|
||||
IF lv_access_token_encrypted IS INITIAL.
|
||||
RAISE EXCEPTION TYPE lcx_2fa_no_cached_token.
|
||||
ENDIF.
|
||||
|
||||
" TODO: Decryption
|
||||
* rv_token = cl_encryption_helper=>decrypt_symmetric( iv_encrypted = rv_access_token
|
||||
* iv_key = iv_password ).
|
||||
rv_token = lv_access_token_encrypted.
|
||||
ENDMETHOD.
|
||||
|
||||
METHOD delete_cached_token.
|
||||
DATA: lx_ex TYPE REF TO cx_root.
|
||||
|
||||
TRY.
|
||||
" Default storage location is user settings
|
||||
lcl_app=>user( )->delete_2fa_config( get_service_id_from_url( iv_url ) ).
|
||||
CATCH lcx_exception INTO lx_ex.
|
||||
RAISE EXCEPTION TYPE lcx_2fa_cache_deletion_failed
|
||||
EXPORTING
|
||||
ix_previous = lx_ex
|
||||
iv_error_text = |Cache deletion failed: { lx_ex->get_text( ) }|.
|
||||
ENDTRY.
|
||||
ENDMETHOD.
|
||||
|
||||
METHOD raise_internal_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_auth_failed
|
||||
EXPORTING
|
||||
iv_error_text = |Internal error: { lv_error_msg }| ##NO_TEXT.
|
||||
ENDMETHOD.
|
||||
ENDCLASS.
|
||||
|
||||
CLASS lcl_2fa_github_authenticator DEFINITION
|
||||
INHERITING FROM lcl_2fa_authenticator_base
|
||||
FINAL
|
||||
CREATE PUBLIC.
|
||||
|
||||
PUBLIC SECTION.
|
||||
METHODS:
|
||||
constructor,
|
||||
get_service_id_from_url REDEFINITION.
|
||||
PROTECTED SECTION.
|
||||
METHODS:
|
||||
authenticate_internal REDEFINITION.
|
||||
PRIVATE SECTION.
|
||||
METHODS:
|
||||
set_access_token_request IMPORTING ii_entity TYPE REF TO if_rest_entity
|
||||
iv_repo_name TYPE string,
|
||||
get_token_from_response IMPORTING ii_entity TYPE REF TO if_rest_entity
|
||||
RETURNING VALUE(rv_token) TYPE string,
|
||||
parse_repo_from_url IMPORTING iv_url TYPE string
|
||||
RETURNING VALUE(rv_repo_name) TYPE string.
|
||||
ENDCLASS.
|
||||
|
||||
CLASS lcl_2fa_github_authenticator IMPLEMENTATION.
|
||||
METHOD constructor.
|
||||
super->constructor( 'https?:\/\/(www\.)?github.com.*$' ).
|
||||
ENDMETHOD.
|
||||
|
||||
METHOD authenticate_internal.
|
||||
CONSTANTS: lc_github_api_url TYPE string VALUE `https://api.github.com/`,
|
||||
lc_otp_header_name TYPE string VALUE `X-Github-OTP`,
|
||||
lc_restendpoint_authorizations TYPE string VALUE `/authorizations`.
|
||||
DATA: li_rest_client TYPE REF TO if_rest_client,
|
||||
li_http_client TYPE REF TO if_http_client,
|
||||
lv_http_code TYPE i,
|
||||
lv_http_code_description TYPE string,
|
||||
li_request_entity TYPE REF TO if_rest_entity,
|
||||
li_response_entity TYPE REF TO if_rest_entity,
|
||||
lv_binary_response TYPE xstring,
|
||||
BEGIN OF ls_success_response,
|
||||
token TYPE string,
|
||||
END OF ls_success_response.
|
||||
|
||||
" 1. Try to login to GitHub API with username, password and 2fa token
|
||||
cl_http_client=>create_by_url(
|
||||
EXPORTING
|
||||
url = lc_github_api_url
|
||||
IMPORTING
|
||||
client = li_http_client
|
||||
EXCEPTIONS
|
||||
argument_not_found = 1
|
||||
plugin_not_active = 2
|
||||
internal_error = 3
|
||||
OTHERS = 4 ).
|
||||
IF sy-subrc <> 0.
|
||||
raise_internal_error_from_sy( ).
|
||||
ENDIF.
|
||||
|
||||
" https://developer.github.com/v3/auth/#working-with-two-factor-authentication
|
||||
li_http_client->propertytype_accept_cookie = if_http_client=>co_enabled.
|
||||
li_http_client->request->set_header_field( name = lc_otp_header_name value = iv_2fa_token ).
|
||||
li_http_client->authenticate( username = iv_username password = iv_password ).
|
||||
li_http_client->propertytype_logon_popup = if_http_client=>co_disabled.
|
||||
|
||||
li_http_client->send( EXCEPTIONS OTHERS = 1 ).
|
||||
IF sy-subrc <> 0.
|
||||
raise_internal_error_from_sy( ).
|
||||
ENDIF.
|
||||
|
||||
li_http_client->receive( EXCEPTIONS OTHERS = 1 ).
|
||||
IF sy-subrc <> 0.
|
||||
raise_internal_error_from_sy( ).
|
||||
ENDIF.
|
||||
|
||||
" Check if authentication has succeeded
|
||||
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_auth_failed
|
||||
EXPORTING
|
||||
iv_error_text = |Authentication failed: { lv_http_code_description }|.
|
||||
ENDIF.
|
||||
|
||||
" Authentication worked, now the rest client can be used
|
||||
CREATE OBJECT li_rest_client TYPE cl_rest_http_client
|
||||
EXPORTING
|
||||
io_http_client = li_http_client.
|
||||
|
||||
|
||||
" 2. Create an access token which can be used instead of a password
|
||||
" https://developer.github.com/v3/oauth_authorizations/#create-a-new-authorization
|
||||
|
||||
li_request_entity = li_rest_client->create_request_entity( ).
|
||||
set_access_token_request( ii_entity = li_request_entity
|
||||
iv_repo_name = parse_repo_from_url( iv_url ) ).
|
||||
li_rest_client->set_request_header( iv_name = if_http_header_fields_sap=>request_uri
|
||||
iv_value = lc_restendpoint_authorizations ).
|
||||
li_rest_client->post( li_request_entity ).
|
||||
|
||||
lv_http_code = li_rest_client->get_status( ).
|
||||
IF lv_http_code <> 201.
|
||||
RAISE EXCEPTION TYPE lcx_2fa_token_gen_failed
|
||||
EXPORTING
|
||||
iv_error_text = |Token generation failed: { lv_http_code }|.
|
||||
ENDIF.
|
||||
|
||||
rv_access_token = get_token_from_response( li_rest_client->get_response_entity( ) ).
|
||||
IF rv_access_token IS INITIAL.
|
||||
RAISE EXCEPTION TYPE lcx_2fa_token_gen_failed.
|
||||
ENDIF.
|
||||
ENDMETHOD.
|
||||
|
||||
METHOD set_access_token_request.
|
||||
CONSTANTS: BEGIN OF lc_create_access_token_request,
|
||||
scopes TYPE string VALUE 'repo',
|
||||
note TYPE string VALUE 'abapGit',
|
||||
END OF lc_create_access_token_request.
|
||||
DATA: lo_json_writer TYPE REF TO cl_sxml_string_writer,
|
||||
lt_scopes TYPE stringtab,
|
||||
lt_rest_parvalues TYPE abap_trans_srcbind_tab,
|
||||
ls_rest_line LIKE LINE OF lt_rest_parvalues,
|
||||
lt_result_parvalues TYPE abap_trans_resbind_tab,
|
||||
ls_result_line LIKE LINE OF lt_result_parvalues,
|
||||
lr_data_ref TYPE REF TO data,
|
||||
lv_note TYPE string,
|
||||
lv_fingerprint TYPE string.
|
||||
|
||||
lo_json_writer = cl_sxml_string_writer=>create( type = if_sxml=>co_xt_json ).
|
||||
APPEND lc_create_access_token_request-scopes TO lt_scopes.
|
||||
|
||||
GET REFERENCE OF lc_create_access_token_request-scopes INTO lr_data_ref.
|
||||
ls_rest_line-name = 'scopes'.
|
||||
ls_rest_line-value = lr_data_ref.
|
||||
APPEND ls_rest_line TO lt_rest_parvalues.
|
||||
|
||||
GET REFERENCE OF lc_create_access_token_request-note INTO lr_data_ref.
|
||||
ls_rest_line-name = 'note'.
|
||||
ls_rest_line-value = lr_data_ref.
|
||||
APPEND ls_rest_line TO lt_rest_parvalues.
|
||||
|
||||
" The fingerprint must be unique, otherwise only one token can be generated, unless the user
|
||||
" deletes it in Github's settings. This is problematic if he deletes it in abapGit but keeps it
|
||||
" on GitHub.
|
||||
lv_fingerprint = |abapGit-{ sy-sysid }-{ sy-uname }-{ sy-datum }-{ sy-uzeit }|.
|
||||
GET REFERENCE OF lv_fingerprint INTO lr_data_ref.
|
||||
ls_rest_line-name = 'fingerprint'.
|
||||
ls_rest_line-value = lr_data_ref.
|
||||
APPEND ls_rest_line TO lt_rest_parvalues.
|
||||
|
||||
" Dynamic source table is used because otherwise identifiers will always be written in uppercase
|
||||
" which is not supported by the GitHub's API.
|
||||
CALL TRANSFORMATION id SOURCE (lt_rest_parvalues)
|
||||
RESULT XML lo_json_writer.
|
||||
|
||||
ii_entity->set_string_data( cl_abap_codepage=>convert_from( lo_json_writer->get_output( ) ) ).
|
||||
ENDMETHOD.
|
||||
|
||||
METHOD get_token_from_response.
|
||||
CONSTANTS: lc_token_field_name TYPE string VALUE 'token'.
|
||||
DATA: lt_result_parvalues TYPE abap_trans_resbind_tab,
|
||||
ls_result_line LIKE LINE OF lt_result_parvalues,
|
||||
lr_data_ref TYPE REF TO data,
|
||||
lv_binary_response TYPE xstring.
|
||||
|
||||
GET REFERENCE OF rv_token INTO lr_data_ref.
|
||||
ls_result_line-name = lc_token_field_name.
|
||||
ls_result_line-value = lr_data_ref.
|
||||
APPEND ls_result_line TO lt_result_parvalues.
|
||||
|
||||
lv_binary_response = ii_entity->get_binary_data( ).
|
||||
CALL TRANSFORMATION id SOURCE XML lv_binary_response
|
||||
RESULT (lt_result_parvalues).
|
||||
ENDMETHOD.
|
||||
|
||||
METHOD parse_repo_from_url.
|
||||
DATA: lo_regex TYPE REF TO cl_abap_regex,
|
||||
lo_matcher TYPE REF TO cl_abap_matcher.
|
||||
|
||||
CREATE OBJECT lo_regex
|
||||
EXPORTING
|
||||
pattern = 'https?:\/\/(www\.)?github.com\/(.*)$'.
|
||||
|
||||
lo_matcher = lo_regex->create_matcher( text = iv_url ).
|
||||
IF lo_matcher->match( ) = abap_true.
|
||||
rv_repo_name = lo_matcher->get_submatch( 1 ).
|
||||
ELSE.
|
||||
rv_repo_name = '???' ##NO_TEXT.
|
||||
ENDIF.
|
||||
ENDMETHOD.
|
||||
|
||||
METHOD get_service_id_from_url.
|
||||
rv_id = 'github'.
|
||||
ENDMETHOD.
|
||||
ENDCLASS.
|
||||
|
||||
"! Static registry class to find <em>LIF_2FA_AUTHENTICATOR</em> instances
|
||||
CLASS lcl_2fa_authenticator_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.
|
||||
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.
|
||||
PROTECTED SECTION.
|
||||
PRIVATE SECTION.
|
||||
ENDCLASS.
|
||||
|
||||
CLASS lcl_2fa_authenticator_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_authenticator.
|
||||
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.
|
||||
ENDCLASS.
|
48
src/zabapgit_2fa.prog.xml
Normal file
48
src/zabapgit_2fa.prog.xml
Normal file
|
@ -0,0 +1,48 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<abapGit version="v1.0.0" serializer="LCL_OBJECT_PROG" serializer_version="v1.0.0">
|
||||
<asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0">
|
||||
<asx:values>
|
||||
<PROGDIR>
|
||||
<NAME>ZABAPGIT_2FA</NAME>
|
||||
<STATE>A</STATE>
|
||||
<SQLX/>
|
||||
<EDTX/>
|
||||
<VARCL>X</VARCL>
|
||||
<DBAPL>S</DBAPL>
|
||||
<DBNA>D$</DBNA>
|
||||
<CLAS/>
|
||||
<TYPE/>
|
||||
<OCCURS/>
|
||||
<SUBC>I</SUBC>
|
||||
<APPL/>
|
||||
<SECU/>
|
||||
<CNAM/>
|
||||
<CDAT>0000-00-00</CDAT>
|
||||
<UNAM/>
|
||||
<UDAT>0000-00-00</UDAT>
|
||||
<VERN/>
|
||||
<LEVL/>
|
||||
<RSTAT/>
|
||||
<RMAND/>
|
||||
<RLOAD/>
|
||||
<FIXPT>X</FIXPT>
|
||||
<SSET/>
|
||||
<SDATE>0000-00-00</SDATE>
|
||||
<STIME/>
|
||||
<IDATE>0000-00-00</IDATE>
|
||||
<ITIME/>
|
||||
<LDBNAME>D$S</LDBNAME>
|
||||
<UCCHECK>X</UCCHECK>
|
||||
</PROGDIR>
|
||||
<TPOOL>
|
||||
<item>
|
||||
<ID>R</ID>
|
||||
<KEY/>
|
||||
<ENTRY>ZABAPGIT_2FA</ENTRY>
|
||||
<LENGTH>12</LENGTH>
|
||||
<SPLIT/>
|
||||
</item>
|
||||
</TPOOL>
|
||||
</asx:values>
|
||||
</asx:abap>
|
||||
</abapGit>
|
|
@ -472,18 +472,79 @@ CLASS lcl_http IMPLEMENTATION.
|
|||
DATA: lv_default_user TYPE string,
|
||||
lv_user TYPE string,
|
||||
lv_pass TYPE string,
|
||||
lo_digest TYPE REF TO lcl_http_digest.
|
||||
lv_2fa_token TYPE string,
|
||||
lv_access_token TYPE string,
|
||||
lo_digest TYPE REF TO lcl_http_digest,
|
||||
lv_2fa_available TYPE abap_bool,
|
||||
li_authenticator TYPE REF TO lif_2fa_authenticator,
|
||||
lx_error TYPE REF TO cx_root,
|
||||
lv_popup_mode TYPE lcl_password_dialog=>gty_mode,
|
||||
lv_popup_requested_token_del TYPE abap_bool,
|
||||
lv_service_id TYPE string.
|
||||
|
||||
|
||||
lv_default_user = lcl_app=>user( )->get_repo_username( iv_url ).
|
||||
lv_user = lv_default_user.
|
||||
|
||||
|
||||
IF lcl_2fa_authenticator_registry=>is_url_supported( iv_url ) = abap_true.
|
||||
TRY.
|
||||
li_authenticator = lcl_2fa_authenticator_registry=>get_authenticator_for_url( iv_url ).
|
||||
lv_service_id = li_authenticator->get_service_id_from_url( iv_url ).
|
||||
IF li_authenticator->is_cached_access_token_avail( iv_url ) = abap_true.
|
||||
lv_popup_mode = lcl_password_dialog=>gc_modes-unlock_2fa_token.
|
||||
ELSE.
|
||||
lv_popup_mode = lcl_password_dialog=>gc_modes-user_pass_2fa.
|
||||
ENDIF.
|
||||
|
||||
CATCH lcx_2fa_error.
|
||||
lv_2fa_available = abap_false.
|
||||
CLEAR lv_access_token.
|
||||
ENDTRY.
|
||||
ELSE.
|
||||
lv_popup_mode = lcl_password_dialog=>gc_modes-user_pass.
|
||||
ENDIF.
|
||||
|
||||
lcl_password_dialog=>popup(
|
||||
EXPORTING
|
||||
iv_repo_url = iv_url
|
||||
iv_mode = lv_popup_mode
|
||||
IMPORTING
|
||||
ev_delete_token = lv_popup_requested_token_del
|
||||
CHANGING
|
||||
cv_user = lv_user
|
||||
cv_pass = lv_pass ).
|
||||
cv_pass = lv_pass
|
||||
cv_2fa_token = lv_2fa_token ).
|
||||
|
||||
IF lv_popup_requested_token_del = abap_true.
|
||||
TRY.
|
||||
li_authenticator->delete_cached_access_token( lv_service_id ).
|
||||
CATCH lcx_2fa_cache_deletion_failed INTO lx_error.
|
||||
RAISE EXCEPTION TYPE lcx_exception
|
||||
EXPORTING
|
||||
iv_text = lx_error->get_text( )
|
||||
ix_previous = lx_error.
|
||||
ENDTRY.
|
||||
|
||||
" Cancel authentication, no credentials were provided. This will cause the next http request
|
||||
" somewhere up the callstack to result in a 401 error.
|
||||
RETURN.
|
||||
ENDIF.
|
||||
|
||||
" Unlock cached access token
|
||||
IF lv_popup_mode = lcl_password_dialog=>gc_modes-unlock_2fa_token.
|
||||
TRY.
|
||||
ASSERT li_authenticator IS BOUND.
|
||||
lv_access_token = li_authenticator->get_cached_access_token( iv_url = iv_url
|
||||
iv_username = lv_user
|
||||
iv_password = lv_pass ).
|
||||
CATCH lcx_2fa_no_cached_token INTO lx_error.
|
||||
RAISE EXCEPTION TYPE lcx_exception
|
||||
EXPORTING
|
||||
iv_text = lx_error->get_text( )
|
||||
ix_previous = lx_error.
|
||||
ENDTRY.
|
||||
ENDIF.
|
||||
|
||||
IF lv_user IS INITIAL.
|
||||
lcx_exception=>raise( 'HTTP 401, unauthorized' ).
|
||||
|
@ -494,6 +555,29 @@ CLASS lcl_http IMPLEMENTATION.
|
|||
iv_username = lv_user ).
|
||||
ENDIF.
|
||||
|
||||
IF lv_access_token IS INITIAL AND lv_2fa_token IS NOT INITIAL.
|
||||
" There is no cached access token but the user provided a two factor token to generate a new
|
||||
" access token
|
||||
TRY.
|
||||
ASSERT li_authenticator IS BOUND.
|
||||
lv_access_token = li_authenticator->authenticate( iv_url = iv_url
|
||||
iv_username = lv_user
|
||||
iv_password = lv_pass
|
||||
iv_2fa_token = lv_2fa_token ).
|
||||
CATCH lcx_2fa_error INTO lx_error.
|
||||
RAISE EXCEPTION TYPE lcx_exception
|
||||
EXPORTING
|
||||
iv_text = lx_error->get_text( )
|
||||
ix_previous = lx_error.
|
||||
ENDTRY.
|
||||
ENDIF.
|
||||
|
||||
" If there is an access token by now use that as the password instead because two factor
|
||||
" authentication was requested.
|
||||
IF lv_access_token IS NOT INITIAL.
|
||||
lv_pass = lv_access_token.
|
||||
ENDIF.
|
||||
|
||||
rv_scheme = ii_client->response->get_header_field( 'www-authenticate' ).
|
||||
FIND REGEX '^(\w+)' IN rv_scheme SUBMATCHES rv_scheme.
|
||||
|
||||
|
|
|
@ -16,6 +16,13 @@ SELECTION-SCREEN BEGIN OF LINE.
|
|||
SELECTION-SCREEN COMMENT 1(10) s_pass FOR FIELD p_pass.
|
||||
PARAMETERS: p_pass TYPE string LOWER CASE VISIBLE LENGTH 40 ##SEL_WRONG.
|
||||
SELECTION-SCREEN END OF LINE.
|
||||
SELECTION-SCREEN BEGIN OF LINE.
|
||||
PARAMETERS: p_en2fa TYPE abap_bool DEFAULT abap_false USER-COMMAND u1 MODIF ID m1 AS CHECKBOX.
|
||||
SELECTION-SCREEN COMMENT 4(6) s_2fat FOR FIELD p_2fat MODIF ID m1.
|
||||
SELECTION-SCREEN POSITION 12.
|
||||
PARAMETERS: p_2fat TYPE string LOWER CASE VISIBLE LENGTH 40 MODIF ID m1.
|
||||
SELECTION-SCREEN END OF LINE.
|
||||
SELECTION-SCREEN FUNCTION KEY 1.
|
||||
SELECTION-SCREEN END OF SCREEN 1002.
|
||||
|
||||
*-----------------------------------------------------------------------
|
||||
|
@ -24,14 +31,26 @@ SELECTION-SCREEN END OF SCREEN 1002.
|
|||
CLASS lcl_password_dialog DEFINITION FINAL.
|
||||
|
||||
PUBLIC SECTION.
|
||||
CONSTANTS dynnr TYPE char4 VALUE '1002'.
|
||||
TYPES:
|
||||
gty_mode TYPE i.
|
||||
CONSTANTS:
|
||||
dynnr TYPE char4 VALUE '1002',
|
||||
BEGIN OF gc_modes,
|
||||
user_pass TYPE gty_mode VALUE 1,
|
||||
user_pass_2fa TYPE gty_mode VALUE 2,
|
||||
unlock_2fa_token TYPE gty_mode VALUE 3,
|
||||
END OF gc_modes.
|
||||
|
||||
CLASS-METHODS popup
|
||||
IMPORTING
|
||||
iv_repo_url TYPE string
|
||||
iv_mode TYPE gty_mode DEFAULT gc_modes-user_pass
|
||||
EXPORTING
|
||||
ev_delete_token TYPE abap_bool
|
||||
CHANGING
|
||||
cv_user TYPE string
|
||||
cv_pass TYPE string.
|
||||
cv_pass TYPE string
|
||||
cv_2fa_token TYPE string.
|
||||
|
||||
CLASS-METHODS on_screen_init.
|
||||
CLASS-METHODS on_screen_output.
|
||||
|
@ -40,7 +59,10 @@ CLASS lcl_password_dialog DEFINITION FINAL.
|
|||
iv_ucomm TYPE syucomm.
|
||||
|
||||
PRIVATE SECTION.
|
||||
CLASS-DATA mv_confirm TYPE abap_bool.
|
||||
CLASS-DATA:
|
||||
mv_confirm TYPE abap_bool,
|
||||
gv_mode TYPE gty_mode,
|
||||
gv_delete_token TYPE abap_bool.
|
||||
|
||||
ENDCLASS. "lcl_password_dialog DEFINITION
|
||||
|
||||
|
@ -52,17 +74,22 @@ CLASS lcl_password_dialog IMPLEMENTATION.
|
|||
p_url = iv_repo_url.
|
||||
p_user = cv_user.
|
||||
mv_confirm = abap_false.
|
||||
gv_mode = iv_mode.
|
||||
gv_delete_token = abap_false.
|
||||
|
||||
CALL SELECTION-SCREEN dynnr STARTING AT 5 5 ENDING AT 60 8.
|
||||
|
||||
IF mv_confirm = abap_true.
|
||||
cv_user = p_user.
|
||||
cv_pass = p_pass.
|
||||
cv_2fa_token = p_2fat.
|
||||
ELSE.
|
||||
CLEAR: cv_user, cv_pass.
|
||||
CLEAR: cv_user, cv_pass, cv_2fa_token.
|
||||
ENDIF.
|
||||
|
||||
CLEAR: p_url, p_user, p_pass.
|
||||
ev_delete_token = gv_delete_token.
|
||||
|
||||
CLEAR: p_url, p_user, p_pass, p_2fat.
|
||||
|
||||
ENDMETHOD. "popup
|
||||
|
||||
|
@ -71,6 +98,7 @@ CLASS lcl_password_dialog IMPLEMENTATION.
|
|||
s_url = 'Repo URL' ##NO_TEXT.
|
||||
s_user = 'User' ##NO_TEXT.
|
||||
s_pass = 'Password' ##NO_TEXT.
|
||||
s_2fat = '2FA' ##NO_TEXT.
|
||||
ENDMETHOD. "on_screen_init
|
||||
|
||||
METHOD on_screen_output.
|
||||
|
@ -78,19 +106,51 @@ CLASS lcl_password_dialog IMPLEMENTATION.
|
|||
|
||||
ASSERT sy-dynnr = dynnr.
|
||||
|
||||
CLEAR p_2fat.
|
||||
|
||||
LOOP AT SCREEN.
|
||||
IF screen-name = 'P_URL'.
|
||||
CASE screen-name.
|
||||
WHEN 'P_URL'.
|
||||
screen-input = '0'.
|
||||
screen-intensified = '1'.
|
||||
screen-display_3d = '0'.
|
||||
MODIFY SCREEN.
|
||||
|
||||
WHEN 'P_PASS'.
|
||||
screen-invisible = '1'.
|
||||
MODIFY SCREEN.
|
||||
|
||||
WHEN 'P_USER'.
|
||||
IF gv_mode = gc_modes-unlock_2fa_token.
|
||||
screen-input = 0.
|
||||
MODIFY SCREEN.
|
||||
ENDIF.
|
||||
IF screen-name = 'P_PASS'.
|
||||
|
||||
WHEN 'P_2FAT'.
|
||||
IF p_en2fa = abap_true.
|
||||
screen-input = '1'.
|
||||
ELSE.
|
||||
screen-input = '0'.
|
||||
p_2fat = 'Two factor authentication token' ##NO_TEXT.
|
||||
ENDIF.
|
||||
MODIFY SCREEN.
|
||||
|
||||
ENDCASE.
|
||||
|
||||
IF screen-group1 = 'M1' AND gv_mode <> gc_modes-user_pass_2fa.
|
||||
screen-invisible = '1'.
|
||||
MODIFY SCREEN.
|
||||
ENDIF.
|
||||
ENDLOOP.
|
||||
|
||||
IF gv_mode = gc_modes-unlock_2fa_token.
|
||||
s_title = 'Unlock two factor authentication token' ##NO_TEXT.
|
||||
sscrfields-functxt_01 = 'Remove token' ##NO_TEXT.
|
||||
ELSE.
|
||||
s_title = 'Login'.
|
||||
CLEAR sscrfields-functxt_01.
|
||||
ENDIF.
|
||||
|
||||
" Program RSSYSTDB, GUI Status %_CSP
|
||||
PERFORM set_pf_status IN PROGRAM rsdbrunt IF FOUND.
|
||||
APPEND 'NONE' TO lt_ucomm. "Button Check
|
||||
|
@ -102,28 +162,42 @@ CLASS lcl_password_dialog IMPLEMENTATION.
|
|||
TABLES
|
||||
p_exclude = lt_ucomm.
|
||||
|
||||
IF p_user IS NOT INITIAL.
|
||||
IF p_user IS NOT INITIAL AND p_pass IS NOT INITIAL AND p_en2fa = abap_true.
|
||||
SET CURSOR FIELD 'P_2FAT'.
|
||||
ELSEIF p_user IS NOT INITIAL.
|
||||
SET CURSOR FIELD 'P_PASS'.
|
||||
ENDIF.
|
||||
|
||||
ENDMETHOD. "on_screen_output
|
||||
|
||||
METHOD on_screen_event.
|
||||
DATA: lv_answer TYPE c.
|
||||
ASSERT sy-dynnr = dynnr.
|
||||
|
||||
" CRET - F8
|
||||
" OTHERS - simulate Enter press
|
||||
CASE iv_ucomm.
|
||||
WHEN 'FC01'. " Delete two factor code
|
||||
CALL FUNCTION 'POPUP_TO_CONFIRM'
|
||||
EXPORTING
|
||||
text_question = 'Do you really want to remove this two factor access token?'
|
||||
IMPORTING
|
||||
answer = lv_answer.
|
||||
IF lv_answer = '1'.
|
||||
gv_delete_token = abap_true.
|
||||
LEAVE TO SCREEN 0.
|
||||
ENDIF.
|
||||
WHEN 'CRET'.
|
||||
mv_confirm = abap_true.
|
||||
LEAVE TO SCREEN 0.
|
||||
WHEN OTHERS. "TODO REFACTOR !!! A CLUTCH !
|
||||
" This will work unless any new specific logic appear
|
||||
" for other commands. The problem is that the password dialog
|
||||
" does not have Enter event (or I don't know how to activate it ;)
|
||||
" so Enter issues previous command from previous screen
|
||||
" But for now this works :) Fortunately Esc produces another flow
|
||||
mv_confirm = abap_true.
|
||||
LEAVE TO SCREEN 0.
|
||||
* mv_confirm = abap_true.
|
||||
* LEAVE TO SCREEN 0.
|
||||
ENDCASE.
|
||||
|
||||
ENDMETHOD. "on_screen_event
|
||||
|
|
|
@ -406,6 +406,21 @@ CLASS lcl_persistence_user DEFINITION FINAL CREATE PRIVATE FRIENDS lcl_app.
|
|||
RETURNING VALUE(rv_email) TYPE string
|
||||
RAISING lcx_exception.
|
||||
|
||||
METHODS get_2fa_access_token
|
||||
IMPORTING iv_service_id TYPE string
|
||||
RETURNING VALUE(rv_token) TYPE string
|
||||
RAISING lcx_exception.
|
||||
|
||||
METHODS set_2fa_access_token
|
||||
IMPORTING iv_service_id TYPE string
|
||||
iv_username TYPE string
|
||||
iv_token TYPE string
|
||||
RAISING lcx_exception.
|
||||
|
||||
METHODS delete_2fa_config
|
||||
IMPORTING iv_service_id TYPE string
|
||||
RAISING lcx_exception.
|
||||
|
||||
METHODS toggle_hide_files
|
||||
RETURNING VALUE(rv_hide) TYPE abap_bool
|
||||
RAISING lcx_exception.
|
||||
|
@ -455,6 +470,13 @@ CLASS lcl_persistence_user DEFINITION FINAL CREATE PRIVATE FRIENDS lcl_app.
|
|||
END OF ty_repo_config.
|
||||
TYPES: ty_repo_config_tt TYPE STANDARD TABLE OF ty_repo_config WITH DEFAULT KEY.
|
||||
|
||||
TYPES: BEGIN OF ty_git_hosting_service,
|
||||
service_id TYPE string,
|
||||
username TYPE string,
|
||||
access_token TYPE string,
|
||||
END OF ty_git_hosting_service.
|
||||
TYPES: ty_git_hosting_service_tab TYPE STANDARD TABLE OF ty_git_hosting_service WITH DEFAULT KEY.
|
||||
|
||||
TYPES: BEGIN OF ty_user,
|
||||
username TYPE string,
|
||||
email TYPE string,
|
||||
|
@ -464,6 +486,7 @@ CLASS lcl_persistence_user DEFINITION FINAL CREATE PRIVATE FRIENDS lcl_app.
|
|||
changes_only TYPE abap_bool,
|
||||
diff_unified TYPE abap_bool,
|
||||
favorites TYPE tt_favorites,
|
||||
two_fact_cfg TYPE ty_git_hosting_service_tab,
|
||||
END OF ty_user.
|
||||
|
||||
METHODS constructor
|
||||
|
@ -496,6 +519,16 @@ CLASS lcl_persistence_user DEFINITION FINAL CREATE PRIVATE FRIENDS lcl_app.
|
|||
is_repo_config TYPE ty_repo_config
|
||||
RAISING lcx_exception.
|
||||
|
||||
METHODS read_2fa_config
|
||||
IMPORTING iv_service_id TYPE ty_git_hosting_service-service_id
|
||||
RETURNING VALUE(rs_2fa_config) TYPE ty_git_hosting_service
|
||||
RAISING lcx_exception.
|
||||
|
||||
METHODS update_2fa_config
|
||||
IMPORTING iv_service_id TYPE ty_git_hosting_service-service_id
|
||||
is_2fa_config TYPE ty_git_hosting_service
|
||||
RAISING lcx_exception.
|
||||
|
||||
ENDCLASS. "lcl_persistence_user DEFINITION
|
||||
|
||||
CLASS lcl_persistence_user IMPLEMENTATION.
|
||||
|
@ -639,6 +672,48 @@ CLASS lcl_persistence_user IMPLEMENTATION.
|
|||
|
||||
ENDMETHOD. "update_repo_config
|
||||
|
||||
METHOD read_2fa_config.
|
||||
DATA: lt_2fa_config TYPE ty_git_hosting_service_tab,
|
||||
lv_key TYPE string.
|
||||
|
||||
lv_key = to_lower( iv_service_id ).
|
||||
lt_2fa_config = read( )-two_fact_cfg.
|
||||
READ TABLE lt_2fa_config INTO rs_2fa_config WITH KEY service_id = lv_key.
|
||||
ENDMETHOD.
|
||||
|
||||
METHOD update_2fa_config.
|
||||
DATA: ls_user TYPE ty_user,
|
||||
lv_key TYPE string.
|
||||
FIELD-SYMBOLS <ls_2fa_config> TYPE ty_git_hosting_service.
|
||||
|
||||
ls_user = read( ).
|
||||
lv_key = to_lower( iv_service_id ).
|
||||
|
||||
READ TABLE ls_user-two_fact_cfg ASSIGNING <ls_2fa_config> WITH KEY service_id = lv_key.
|
||||
IF sy-subrc IS NOT INITIAL.
|
||||
APPEND INITIAL LINE TO ls_user-two_fact_cfg ASSIGNING <ls_2fa_config>.
|
||||
ENDIF.
|
||||
<ls_2fa_config> = is_2fa_config.
|
||||
<ls_2fa_config>-service_id = lv_key.
|
||||
|
||||
update( ls_user ).
|
||||
ENDMETHOD.
|
||||
|
||||
METHOD delete_2fa_config.
|
||||
DATA: ls_user TYPE ty_user,
|
||||
lv_key TYPE string.
|
||||
|
||||
ls_user = read( ).
|
||||
lv_key = to_lower( iv_service_id ).
|
||||
|
||||
DELETE ls_user-two_fact_cfg WHERE service_id = lv_key.
|
||||
IF sy-subrc <> 0.
|
||||
lcx_exception=>raise( '2FA config could not be deleted.' ) ##NO_TEXT.
|
||||
ENDIF.
|
||||
|
||||
update( ls_user ).
|
||||
ENDMETHOD.
|
||||
|
||||
METHOD set_repo_username.
|
||||
|
||||
DATA: ls_repo_config TYPE ty_repo_config.
|
||||
|
@ -671,6 +746,21 @@ CLASS lcl_persistence_user IMPLEMENTATION.
|
|||
|
||||
ENDMETHOD. "get_repo_email
|
||||
|
||||
METHOD get_2fa_access_token.
|
||||
rv_token = read_2fa_config( iv_service_id )-access_token.
|
||||
ENDMETHOD.
|
||||
|
||||
METHOD set_2fa_access_token.
|
||||
DATA: ls_config TYPE ty_git_hosting_service.
|
||||
|
||||
ls_config = read_2fa_config( iv_service_id ).
|
||||
ls_config-service_id = iv_service_id.
|
||||
ls_config-username = iv_username.
|
||||
ls_config-access_token = iv_token.
|
||||
|
||||
update_2fa_config( iv_service_id = iv_service_id is_2fa_config = ls_config ).
|
||||
ENDMETHOD.
|
||||
|
||||
METHOD toggle_hide_files.
|
||||
|
||||
DATA ls_user TYPE ty_user.
|
||||
|
|
Loading…
Reference in New Issue
Block a user