mirror of
https://github.com/abapGit/abapGit.git
synced 2025-05-01 12:20:51 +08:00
Merge remote-tracking branch 'refs/remotes/larshp/master'
This commit is contained in:
commit
6f9ea7e68b
|
@ -8,6 +8,10 @@ Legend
|
||||||
+ : added
|
+ : added
|
||||||
- : removed
|
- : removed
|
||||||
|
|
||||||
|
2017-02-25 v1.27.0
|
||||||
|
------------------
|
||||||
|
+ Two factor authentication with github.com
|
||||||
|
|
||||||
2017-01-22 v1.26.0
|
2017-01-22 v1.26.0
|
||||||
------------------
|
------------------
|
||||||
+ XML ignore initial fields
|
+ XML ignore initial fields
|
||||||
|
|
|
@ -3,7 +3,7 @@ REPORT zabapgit LINE-SIZE 100.
|
||||||
* See http://www.abapgit.org
|
* See http://www.abapgit.org
|
||||||
|
|
||||||
CONSTANTS: gc_xml_version TYPE string VALUE 'v1.0.0', "#EC NOTEXT
|
CONSTANTS: gc_xml_version TYPE string VALUE 'v1.0.0', "#EC NOTEXT
|
||||||
gc_abap_version TYPE string VALUE 'v1.26.5'. "#EC NOTEXT
|
gc_abap_version TYPE string VALUE 'v1.27.0'. "#EC NOTEXT
|
||||||
|
|
||||||
********************************************************************************
|
********************************************************************************
|
||||||
* The MIT License (MIT)
|
* The MIT License (MIT)
|
||||||
|
@ -53,6 +53,7 @@ INCLUDE zabapgit_stage.
|
||||||
INCLUDE zabapgit_git_helpers.
|
INCLUDE zabapgit_git_helpers.
|
||||||
INCLUDE zabapgit_repo.
|
INCLUDE zabapgit_repo.
|
||||||
INCLUDE zabapgit_stage_logic.
|
INCLUDE zabapgit_stage_logic.
|
||||||
|
INCLUDE zabapgit_2fa.
|
||||||
INCLUDE zabapgit_http.
|
INCLUDE zabapgit_http.
|
||||||
INCLUDE zabapgit_git.
|
INCLUDE zabapgit_git.
|
||||||
INCLUDE zabapgit_objects.
|
INCLUDE zabapgit_objects.
|
||||||
|
|
792
src/zabapgit_2fa.prog.abap
Normal file
792
src/zabapgit_2fa.prog.abap
Normal file
|
@ -0,0 +1,792 @@
|
||||||
|
*&---------------------------------------------------------------------*
|
||||||
|
*& 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_token_del_failed DEFINITION INHERITING FROM lcx_2fa_error FINAL.
|
||||||
|
PROTECTED SECTION.
|
||||||
|
METHODS:
|
||||||
|
get_default_text REDEFINITION.
|
||||||
|
ENDCLASS.
|
||||||
|
|
||||||
|
CLASS lcx_2fa_token_del_failed IMPLEMENTATION.
|
||||||
|
METHOD get_default_text.
|
||||||
|
rv_text = 'Deleting previous access tokens failed.' ##NO_TEXT.
|
||||||
|
ENDMETHOD.
|
||||||
|
ENDCLASS.
|
||||||
|
|
||||||
|
CLASS lcx_2fa_communication_error DEFINITION INHERITING FROM lcx_2fa_error FINAL.
|
||||||
|
PROTECTED SECTION.
|
||||||
|
METHODS:
|
||||||
|
get_default_text REDEFINITION.
|
||||||
|
ENDCLASS.
|
||||||
|
|
||||||
|
CLASS lcx_2fa_communication_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_token_gen_failed
|
||||||
|
lcx_2fa_communication_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_communication_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_token_del_failed
|
||||||
|
lcx_2fa_communication_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_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_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_communication_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_authenticator_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_token_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_communication_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_authenticator DEFINITION
|
||||||
|
INHERITING FROM lcl_2fa_authenticator_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,
|
||||||
|
parse_repo_from_url IMPORTING iv_url TYPE string
|
||||||
|
RETURNING VALUE(rv_repo_name) 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_communication_error.
|
||||||
|
DATA:
|
||||||
|
mi_authenticated_session TYPE REF TO if_http_client.
|
||||||
|
ENDCLASS.
|
||||||
|
|
||||||
|
CLASS lcl_2fa_github_authenticator 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_token_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_token_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 parse_repo_from_url.
|
||||||
|
CONSTANTS: lc_search_regex TYPE string VALUE 'https?:\/\/(www\.)?github.com\/(.*)$'.
|
||||||
|
DATA: lo_regex TYPE REF TO cl_abap_regex,
|
||||||
|
lo_matcher TYPE REF TO cl_abap_matcher.
|
||||||
|
|
||||||
|
CREATE OBJECT lo_regex
|
||||||
|
EXPORTING
|
||||||
|
pattern = lc_search_regex.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
METHOD is_2fa_required.
|
||||||
|
DATA: li_client TYPE REF TO if_http_client,
|
||||||
|
lv_header_value TYPE string,
|
||||||
|
lo_settings TYPE REF TO lcl_settings.
|
||||||
|
|
||||||
|
lo_settings = lcl_app=>settings( )->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 = 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_token_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_token_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 lcl_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 = lcl_app=>settings( )->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_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,
|
||||||
|
"! 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 lcx_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 lcx_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.
|
||||||
|
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.
|
||||||
|
|
||||||
|
METHOD use_2fa_if_required.
|
||||||
|
DATA: li_authenticator TYPE REF TO lif_2fa_authenticator,
|
||||||
|
lv_2fa_token TYPE string,
|
||||||
|
lv_use_2fa TYPE abap_bool,
|
||||||
|
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.
|
||||||
|
|
||||||
|
" Get a 2FA token (app/sms)
|
||||||
|
CALL FUNCTION 'POPUP_GET_STRING'
|
||||||
|
EXPORTING
|
||||||
|
label = 'Two factor auth. token'
|
||||||
|
IMPORTING
|
||||||
|
value = lv_2fa_token
|
||||||
|
okay = lv_use_2fa.
|
||||||
|
IF lv_use_2fa = abap_false.
|
||||||
|
lcx_exception=>raise( 'Authentication cancelled' ).
|
||||||
|
ENDIF.
|
||||||
|
|
||||||
|
" 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.
|
||||||
|
|
||||||
|
RAISE EXCEPTION TYPE lcx_exception
|
||||||
|
EXPORTING
|
||||||
|
iv_text = |2FA error: { lx_ex->get_text( ) }|
|
||||||
|
ix_previous = lx_ex.
|
||||||
|
ENDTRY.
|
||||||
|
ENDMETHOD.
|
||||||
|
ENDCLASS.
|
25
src/zabapgit_2fa.prog.xml
Normal file
25
src/zabapgit_2fa.prog.xml
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<?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>
|
||||||
|
<VARCL>X</VARCL>
|
||||||
|
<DBAPL>S</DBAPL>
|
||||||
|
<DBNA>D$</DBNA>
|
||||||
|
<SUBC>I</SUBC>
|
||||||
|
<FIXPT>X</FIXPT>
|
||||||
|
<LDBNAME>D$S</LDBNAME>
|
||||||
|
<UCCHECK>X</UCCHECK>
|
||||||
|
</PROGDIR>
|
||||||
|
<TPOOL>
|
||||||
|
<item>
|
||||||
|
<ID>R</ID>
|
||||||
|
<ENTRY>ZABAPGIT_2FA</ENTRY>
|
||||||
|
<LENGTH>12</LENGTH>
|
||||||
|
</item>
|
||||||
|
</TPOOL>
|
||||||
|
</asx:values>
|
||||||
|
</asx:abap>
|
||||||
|
</abapGit>
|
|
@ -37,6 +37,8 @@ form input:focus, textarea:focus {
|
||||||
.grey { color: lightgrey !important; }
|
.grey { color: lightgrey !important; }
|
||||||
.darkgrey { color: #808080 !important; }
|
.darkgrey { color: #808080 !important; }
|
||||||
.attention { color: red !important; }
|
.attention { color: red !important; }
|
||||||
|
.error { color: #d41919 !important; }
|
||||||
|
.warning { color: #e4ae0d !important; }
|
||||||
.blue { color: #5e8dc9 !important; }
|
.blue { color: #5e8dc9 !important; }
|
||||||
.red { color: red !important; }
|
.red { color: red !important; }
|
||||||
|
|
||||||
|
@ -105,6 +107,19 @@ span.page_title {
|
||||||
padding-left: 0.4em;
|
padding-left: 0.4em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ERROR LOG */
|
||||||
|
|
||||||
|
div.log {
|
||||||
|
padding: 6px;
|
||||||
|
margin: 4px;
|
||||||
|
background-color: #fee6e6;
|
||||||
|
border: 1px #fdcece solid;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.log > span { display:block; }
|
||||||
|
div.log .octicon { padding-right: 6px; }
|
||||||
|
|
||||||
/* MENU */
|
/* MENU */
|
||||||
div.menu { display: inline; }
|
div.menu { display: inline; }
|
||||||
div.menu .menu_end { border-right: 0px !important; }
|
div.menu .menu_end { border-right: 0px !important; }
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0">
|
<asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0">
|
||||||
<asx:values>
|
<asx:values>
|
||||||
<NAME>ZABAPGIT_CSS_COMMON</NAME>
|
<NAME>ZABAPGIT_CSS_COMMON</NAME>
|
||||||
<TEXT/>
|
<TEXT>Abapgit common CSS</TEXT>
|
||||||
<PARAMS>
|
<PARAMS>
|
||||||
<WWWPARAMS>
|
<WWWPARAMS>
|
||||||
<RELID>MI</RELID>
|
<RELID>MI</RELID>
|
||||||
|
@ -15,7 +15,7 @@
|
||||||
<RELID>MI</RELID>
|
<RELID>MI</RELID>
|
||||||
<OBJID>ZABAPGIT_CSS_COMMON</OBJID>
|
<OBJID>ZABAPGIT_CSS_COMMON</OBJID>
|
||||||
<NAME>filename</NAME>
|
<NAME>filename</NAME>
|
||||||
<VALUE>~wwwtmp.css</VALUE>
|
<VALUE>common.css</VALUE>
|
||||||
</WWWPARAMS>
|
</WWWPARAMS>
|
||||||
<WWWPARAMS>
|
<WWWPARAMS>
|
||||||
<RELID>MI</RELID>
|
<RELID>MI</RELID>
|
||||||
|
|
|
@ -227,6 +227,7 @@ CONSTANTS: BEGIN OF gc_action,
|
||||||
go_debuginfo TYPE string VALUE 'go_debuginfo',
|
go_debuginfo TYPE string VALUE 'go_debuginfo',
|
||||||
go_settings TYPE string VALUE 'go_settings',
|
go_settings TYPE string VALUE 'go_settings',
|
||||||
go_tutorial TYPE string VALUE 'go_tutorial',
|
go_tutorial TYPE string VALUE 'go_tutorial',
|
||||||
|
|
||||||
jump TYPE string VALUE 'jump',
|
jump TYPE string VALUE 'jump',
|
||||||
jump_pkg TYPE string VALUE 'jump_pkg',
|
jump_pkg TYPE string VALUE 'jump_pkg',
|
||||||
END OF gc_action.
|
END OF gc_action.
|
||||||
|
|
|
@ -35,8 +35,8 @@ CLASS lcl_gui_router DEFINITION FINAL.
|
||||||
RAISING lcx_exception.
|
RAISING lcx_exception.
|
||||||
|
|
||||||
METHODS get_page_stage
|
METHODS get_page_stage
|
||||||
IMPORTING iv_key TYPE lcl_persistence_repo=>ty_repo-key
|
IMPORTING iv_key TYPE lcl_persistence_repo=>ty_repo-key
|
||||||
RETURNING VALUE(ri_page) TYPE REF TO lif_gui_page
|
RETURNING VALUE(ri_page) TYPE REF TO lif_gui_page
|
||||||
RAISING lcx_exception.
|
RAISING lcx_exception.
|
||||||
|
|
||||||
METHODS get_page_db_by_name
|
METHODS get_page_db_by_name
|
||||||
|
@ -310,7 +310,7 @@ CLASS lcl_gui_router IMPLEMENTATION.
|
||||||
|
|
||||||
CREATE OBJECT lo_stage_page
|
CREATE OBJECT lo_stage_page
|
||||||
EXPORTING
|
EXPORTING
|
||||||
io_repo = lo_repo.
|
io_repo = lo_repo.
|
||||||
|
|
||||||
ri_page = lo_stage_page.
|
ri_page = lo_stage_page.
|
||||||
|
|
||||||
|
|
|
@ -469,10 +469,10 @@ CLASS lcl_http IMPLEMENTATION.
|
||||||
|
|
||||||
METHOD acquire_login_details.
|
METHOD acquire_login_details.
|
||||||
|
|
||||||
DATA: lv_default_user TYPE string,
|
DATA: lv_default_user TYPE string,
|
||||||
lv_user TYPE string,
|
lv_user TYPE string,
|
||||||
lv_pass TYPE string,
|
lv_pass TYPE string,
|
||||||
lo_digest TYPE REF TO lcl_http_digest.
|
lo_digest TYPE REF TO lcl_http_digest.
|
||||||
|
|
||||||
|
|
||||||
lv_default_user = lcl_app=>user( )->get_repo_username( iv_url ).
|
lv_default_user = lcl_app=>user( )->get_repo_username( iv_url ).
|
||||||
|
@ -480,10 +480,10 @@ CLASS lcl_http IMPLEMENTATION.
|
||||||
|
|
||||||
lcl_password_dialog=>popup(
|
lcl_password_dialog=>popup(
|
||||||
EXPORTING
|
EXPORTING
|
||||||
iv_repo_url = iv_url
|
iv_repo_url = iv_url
|
||||||
CHANGING
|
CHANGING
|
||||||
cv_user = lv_user
|
cv_user = lv_user
|
||||||
cv_pass = lv_pass ).
|
cv_pass = lv_pass ).
|
||||||
|
|
||||||
IF lv_user IS INITIAL.
|
IF lv_user IS INITIAL.
|
||||||
lcx_exception=>raise( 'HTTP 401, unauthorized' ).
|
lcx_exception=>raise( 'HTTP 401, unauthorized' ).
|
||||||
|
@ -494,6 +494,14 @@ CLASS lcl_http IMPLEMENTATION.
|
||||||
iv_username = lv_user ).
|
iv_username = lv_user ).
|
||||||
ENDIF.
|
ENDIF.
|
||||||
|
|
||||||
|
" Offer two factor authentication if it is available and required
|
||||||
|
lcl_2fa_authenticator_registry=>use_2fa_if_required(
|
||||||
|
EXPORTING
|
||||||
|
iv_url = iv_url
|
||||||
|
CHANGING
|
||||||
|
cv_username = lv_user
|
||||||
|
cv_password = lv_pass ).
|
||||||
|
|
||||||
rv_scheme = ii_client->response->get_header_field( 'www-authenticate' ).
|
rv_scheme = ii_client->response->get_header_field( 'www-authenticate' ).
|
||||||
FIND REGEX '^(\w+)' IN rv_scheme SUBMATCHES rv_scheme.
|
FIND REGEX '^(\w+)' IN rv_scheme SUBMATCHES rv_scheme.
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,18 @@ CLASS lcl_object_tobj IMPLEMENTATION.
|
||||||
ENDMETHOD. "lif_object~has_changed_since
|
ENDMETHOD. "lif_object~has_changed_since
|
||||||
|
|
||||||
METHOD lif_object~changed_by.
|
METHOD lif_object~changed_by.
|
||||||
rv_user = c_user_unknown. " todo
|
|
||||||
|
DATA: lv_type_pos TYPE i.
|
||||||
|
|
||||||
|
lv_type_pos = strlen( ms_item-obj_name ) - 1.
|
||||||
|
|
||||||
|
SELECT SINGLE luser FROM objh INTO rv_user
|
||||||
|
WHERE objectname = ms_item-obj_name(lv_type_pos)
|
||||||
|
AND objecttype = ms_item-obj_name+lv_type_pos. "#EC CI_GENBUFF
|
||||||
|
IF sy-subrc <> 0.
|
||||||
|
rv_user = c_user_unknown.
|
||||||
|
ENDIF.
|
||||||
|
|
||||||
ENDMETHOD.
|
ENDMETHOD.
|
||||||
|
|
||||||
METHOD lif_object~get_metadata.
|
METHOD lif_object~get_metadata.
|
||||||
|
|
|
@ -12,7 +12,8 @@ CLASS lcl_gui_page_stage DEFINITION FINAL INHERITING FROM lcl_gui_page.
|
||||||
|
|
||||||
METHODS:
|
METHODS:
|
||||||
constructor
|
constructor
|
||||||
IMPORTING io_repo TYPE REF TO lcl_repo_online
|
IMPORTING
|
||||||
|
io_repo TYPE REF TO lcl_repo_online
|
||||||
RAISING lcx_exception,
|
RAISING lcx_exception,
|
||||||
lif_gui_page~on_event REDEFINITION.
|
lif_gui_page~on_event REDEFINITION.
|
||||||
|
|
||||||
|
@ -35,7 +36,10 @@ CLASS lcl_gui_page_stage DEFINITION FINAL INHERITING FROM lcl_gui_page.
|
||||||
iv_context TYPE string
|
iv_context TYPE string
|
||||||
RETURNING VALUE(ro_html) TYPE REF TO lcl_html,
|
RETURNING VALUE(ro_html) TYPE REF TO lcl_html,
|
||||||
render_menu
|
render_menu
|
||||||
RETURNING VALUE(ro_html) TYPE REF TO lcl_html.
|
RETURNING VALUE(ro_html) TYPE REF TO lcl_html,
|
||||||
|
read_last_changed_by
|
||||||
|
IMPORTING is_file TYPE ty_file
|
||||||
|
RETURNING VALUE(rv_user) TYPE xubname.
|
||||||
|
|
||||||
METHODS process_stage_list
|
METHODS process_stage_list
|
||||||
IMPORTING it_postdata TYPE cnht_post_data_tab
|
IMPORTING it_postdata TYPE cnht_post_data_tab
|
||||||
|
@ -66,7 +70,6 @@ CLASS lcl_gui_page_stage IMPLEMENTATION.
|
||||||
|
|
||||||
FIELD-SYMBOLS: <ls_file> LIKE LINE OF ms_files-local.
|
FIELD-SYMBOLS: <ls_file> LIKE LINE OF ms_files-local.
|
||||||
|
|
||||||
|
|
||||||
CASE iv_action.
|
CASE iv_action.
|
||||||
WHEN c_action-stage_all.
|
WHEN c_action-stage_all.
|
||||||
mo_stage->reset_all( ).
|
mo_stage->reset_all( ).
|
||||||
|
@ -112,7 +115,6 @@ CLASS lcl_gui_page_stage IMPLEMENTATION.
|
||||||
lcl_path=>split_file_location( EXPORTING iv_fullpath = <ls_item>-name
|
lcl_path=>split_file_location( EXPORTING iv_fullpath = <ls_item>-name
|
||||||
IMPORTING ev_path = ls_file-path
|
IMPORTING ev_path = ls_file-path
|
||||||
ev_filename = ls_file-filename ).
|
ev_filename = ls_file-filename ).
|
||||||
|
|
||||||
CASE <ls_item>-value.
|
CASE <ls_item>-value.
|
||||||
WHEN lcl_stage=>c_method-add.
|
WHEN lcl_stage=>c_method-add.
|
||||||
READ TABLE ms_files-local ASSIGNING <ls_file>
|
READ TABLE ms_files-local ASSIGNING <ls_file>
|
||||||
|
@ -142,7 +144,6 @@ CLASS lcl_gui_page_stage IMPLEMENTATION.
|
||||||
FIELD-SYMBOLS: <ls_remote> LIKE LINE OF ms_files-remote,
|
FIELD-SYMBOLS: <ls_remote> LIKE LINE OF ms_files-remote,
|
||||||
<ls_local> LIKE LINE OF ms_files-local.
|
<ls_local> LIKE LINE OF ms_files-local.
|
||||||
|
|
||||||
|
|
||||||
CREATE OBJECT ro_html.
|
CREATE OBJECT ro_html.
|
||||||
|
|
||||||
ro_html->add( '<table id="stage_tab" class="stage_tab">' ).
|
ro_html->add( '<table id="stage_tab" class="stage_tab">' ).
|
||||||
|
@ -158,6 +159,7 @@ CLASS lcl_gui_page_stage IMPLEMENTATION.
|
||||||
iv_act = |{ gc_action-go_diff }?key={ mo_repo->get_key( ) }| ).
|
iv_act = |{ gc_action-go_diff }?key={ mo_repo->get_key( ) }| ).
|
||||||
ENDIF.
|
ENDIF.
|
||||||
ro_html->add('</th>').
|
ro_html->add('</th>').
|
||||||
|
ro_html->add('<th>Last changed by</th>').
|
||||||
ro_html->add('</tr></thead>').
|
ro_html->add('</tr></thead>').
|
||||||
ro_html->add('<tbody class="local">').
|
ro_html->add('<tbody class="local">').
|
||||||
ENDAT.
|
ENDAT.
|
||||||
|
@ -191,7 +193,8 @@ CLASS lcl_gui_page_stage IMPLEMENTATION.
|
||||||
|
|
||||||
METHOD render_file.
|
METHOD render_file.
|
||||||
|
|
||||||
DATA lv_param TYPE string.
|
DATA: lv_param TYPE string,
|
||||||
|
lv_user TYPE xubname.
|
||||||
|
|
||||||
CREATE OBJECT ro_html.
|
CREATE OBJECT ro_html.
|
||||||
|
|
||||||
|
@ -207,6 +210,9 @@ CLASS lcl_gui_page_stage IMPLEMENTATION.
|
||||||
ro_html->add( '<td>' ).
|
ro_html->add( '<td>' ).
|
||||||
ro_html->add_a( iv_txt = 'diff' iv_act = |{ gc_action-go_diff }?{ lv_param }| ).
|
ro_html->add_a( iv_txt = 'diff' iv_act = |{ gc_action-go_diff }?{ lv_param }| ).
|
||||||
ro_html->add( '</td>' ).
|
ro_html->add( '</td>' ).
|
||||||
|
|
||||||
|
lv_user = read_last_changed_by( is_file ).
|
||||||
|
ro_html->add( |<td>{ lv_user }</td> | ).
|
||||||
WHEN 'remote'.
|
WHEN 'remote'.
|
||||||
ro_html->add( '<td class="cmd"><a>ignore</a><a>remove</a></td>' ).
|
ro_html->add( '<td class="cmd"><a>ignore</a><a>remove</a></td>' ).
|
||||||
ro_html->add( |<td><span class="grey">-</span></td>| ).
|
ro_html->add( |<td><span class="grey">-</span></td>| ).
|
||||||
|
@ -263,4 +269,18 @@ CLASS lcl_gui_page_stage IMPLEMENTATION.
|
||||||
|
|
||||||
ENDMETHOD. "scripts
|
ENDMETHOD. "scripts
|
||||||
|
|
||||||
|
METHOD read_last_changed_by.
|
||||||
|
DATA: ls_local_file TYPE ty_file_item,
|
||||||
|
lt_files_local type ty_files_item_tt.
|
||||||
|
TRY.
|
||||||
|
lt_files_local = mo_repo->get_files_local( ).
|
||||||
|
READ TABLE lt_files_local INTO ls_local_file WITH KEY file = is_file.
|
||||||
|
IF sy-subrc = 0.
|
||||||
|
rv_user = lcl_objects=>changed_by( ls_local_file-item ).
|
||||||
|
ENDIF.
|
||||||
|
CATCH lcx_exception.
|
||||||
|
CLEAR rv_user. "Should not raise errors if user last changed by was not found
|
||||||
|
ENDTRY.
|
||||||
|
ENDMETHOD.
|
||||||
|
|
||||||
ENDCLASS.
|
ENDCLASS.
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
*&----------------------------
|
*&---------------------------------------------------------------------*
|
||||||
*& Include ZABAPGIT_PERSISTENCE
|
*& Include ZABAPGIT_PERSISTENCE
|
||||||
*&---------------------------------------------------------------------*
|
*&---------------------------------------------------------------------*
|
||||||
|
|
||||||
|
|
|
@ -30,11 +30,13 @@ ENDCLASS.
|
||||||
CLASS lcl_stage_logic IMPLEMENTATION.
|
CLASS lcl_stage_logic IMPLEMENTATION.
|
||||||
|
|
||||||
METHOD get.
|
METHOD get.
|
||||||
|
|
||||||
rs_files-local = io_repo->get_files_local( ).
|
rs_files-local = io_repo->get_files_local( ).
|
||||||
rs_files-remote = io_repo->get_files_remote( ).
|
rs_files-remote = io_repo->get_files_remote( ).
|
||||||
remove_identical( CHANGING cs_files = rs_files ).
|
remove_identical( CHANGING cs_files = rs_files ).
|
||||||
remove_ignored( EXPORTING io_repo = io_repo
|
remove_ignored( EXPORTING io_repo = io_repo
|
||||||
CHANGING cs_files = rs_files ).
|
CHANGING cs_files = rs_files ).
|
||||||
|
|
||||||
ENDMETHOD.
|
ENDMETHOD.
|
||||||
|
|
||||||
METHOD count.
|
METHOD count.
|
||||||
|
@ -58,10 +60,14 @@ CLASS lcl_stage_logic IMPLEMENTATION.
|
||||||
lv_index = sy-tabix.
|
lv_index = sy-tabix.
|
||||||
|
|
||||||
IF io_repo->get_dot_abapgit( )->is_ignored(
|
IF io_repo->get_dot_abapgit( )->is_ignored(
|
||||||
iv_path = <ls_remote>-path
|
iv_path = <ls_remote>-path
|
||||||
iv_filename = <ls_remote>-filename ) = abap_true.
|
iv_filename = <ls_remote>-filename ) = abap_true.
|
||||||
DELETE cs_files-remote INDEX lv_index.
|
DELETE cs_files-remote INDEX lv_index.
|
||||||
|
ELSEIF <ls_remote>-path = gc_root_dir AND <ls_remote>-filename = gc_dot_abapgit.
|
||||||
|
" Remove .abapgit from remotes - it cannot be removed or ignored
|
||||||
|
DELETE cs_files-remote INDEX lv_index.
|
||||||
ENDIF.
|
ENDIF.
|
||||||
|
|
||||||
ENDLOOP.
|
ENDLOOP.
|
||||||
|
|
||||||
ENDMETHOD.
|
ENDMETHOD.
|
||||||
|
|
|
@ -1069,16 +1069,14 @@ CLASS lcl_log IMPLEMENTATION.
|
||||||
RETURN.
|
RETURN.
|
||||||
ENDIF.
|
ENDIF.
|
||||||
|
|
||||||
ro_html->add( '<br>' ).
|
|
||||||
LOOP AT mt_log ASSIGNING <ls_log>.
|
LOOP AT mt_log ASSIGNING <ls_log>.
|
||||||
CONCATENATE <ls_log>-msgv1
|
CONCATENATE <ls_log>-msgv1 <ls_log>-msgv2 <ls_log>-msgv3 <ls_log>-msgv4
|
||||||
<ls_log>-msgv2
|
INTO lv_string SEPARATED BY space.
|
||||||
<ls_log>-msgv3
|
ro_html->add( '<span class="error">' ).
|
||||||
<ls_log>-msgv4 INTO lv_string SEPARATED BY space.
|
ro_html->add_icon( iv_name = 'alert' iv_class = 'error' ). " warning CSS exists too
|
||||||
ro_html->add( lv_string ).
|
ro_html->add( lv_string ).
|
||||||
ro_html->add( '<br>' ).
|
ro_html->add( '</span>' ).
|
||||||
ENDLOOP.
|
ENDLOOP.
|
||||||
ro_html->add( '<br>' ).
|
|
||||||
|
|
||||||
ENDMETHOD.
|
ENDMETHOD.
|
||||||
|
|
||||||
|
|
|
@ -146,7 +146,7 @@ CLASS lcl_gui_view_repo_content IMPLEMENTATION.
|
||||||
|
|
||||||
lo_log = lo_browser->get_log( ).
|
lo_log = lo_browser->get_log( ).
|
||||||
IF mo_repo->is_offline( ) = abap_false AND lo_log->count( ) > 0.
|
IF mo_repo->is_offline( ) = abap_false AND lo_log->count( ) > 0.
|
||||||
ro_html->add( '<div class="log attention">' ).
|
ro_html->add( '<div class="log">' ).
|
||||||
ro_html->add( lo_log->to_html( ) ). " shows eg. list of unsupported objects
|
ro_html->add( lo_log->to_html( ) ). " shows eg. list of unsupported objects
|
||||||
ro_html->add( '</div>' ).
|
ro_html->add( '</div>' ).
|
||||||
ENDIF.
|
ENDIF.
|
||||||
|
@ -278,6 +278,10 @@ CLASS lcl_gui_view_repo_content IMPLEMENTATION.
|
||||||
iv_act = |{ gc_action-repo_remote_change }?{ lv_key }| ).
|
iv_act = |{ gc_action-repo_remote_change }?{ lv_key }| ).
|
||||||
lo_tb_advanced->add( iv_txt = 'Make off-line'
|
lo_tb_advanced->add( iv_txt = 'Make off-line'
|
||||||
iv_act = |{ gc_action-repo_remote_detach }?{ lv_key }| ).
|
iv_act = |{ gc_action-repo_remote_detach }?{ lv_key }| ).
|
||||||
|
IF iv_rstate IS INITIAL AND iv_lstate IS INITIAL.
|
||||||
|
lo_tb_advanced->add( iv_txt = 'Force stage'
|
||||||
|
iv_act = |{ gc_action-go_stage }?{ lv_key }| ).
|
||||||
|
ENDIF.
|
||||||
ELSE.
|
ELSE.
|
||||||
lo_tb_advanced->add( iv_txt = 'Make on-line'
|
lo_tb_advanced->add( iv_txt = 'Make on-line'
|
||||||
iv_act = |{ gc_action-repo_remote_attach }?{ lv_key }| ).
|
iv_act = |{ gc_action-repo_remote_attach }?{ lv_key }| ).
|
||||||
|
|
Loading…
Reference in New Issue
Block a user