How are Security and Authorization Policies Implemented in SAP OData Services?
Emre Göçmen
Author

How to Implement Security and Authorization Policies in SAP OData Services
While SAP OData services provide flexibility and power in data access, securing these services is of critical importance. In this comprehensive guide, we will address the security and authorization policies you need to implement to protect your SAP OData services.
Authentication and Authorization
1. Authentication Methods
Implementing strong authentication mechanisms for your OData services is a cornerstone of security:
• Basic Authentication: The simplest method, but should only be used with HTTPS.
* Basic Authentication Configuration in SAP Gateway
* Service configuration in SICF transaction
DATA: lo_server TYPE REF TO if_http_server,
lo_authentication TYPE REF TO if_http_authentication.
lo_server ?= server.
lo_authentication = lo_server->get_authentication( ).
* Basic authentication check
IF lo_authentication->authenticate( ) NE abap_true.
lo_server->response->set_status( code = 401 reason = 'Unauthorized' ).
lo_server->response->set_header_field( name = 'WWW-Authenticate' value = 'Basic' ).
RETURN.
ENDIF.
• SAP Logon Ticket: Used for Single Sign-On (SSO) between SAP systems.
* SAP Logon Ticket validation
DATA: lv_ticket TYPE string,
ls_ticket_info TYPE STANDARD TABLE OF bapiticket.
* Get ticket from HTTP header
lv_ticket = server->request->get_header_field( 'SAP-PASSPORT' ).
* Validate the ticket
CALL FUNCTION 'RSTS_EVALUATE_TICKET'
EXPORTING
ticket = lv_ticket
TABLES
ticket_info = ls_ticket_info
EXCEPTIONS
ticket_not_found = 1
invalid_signature = 2
invalid_ticket = 3
OTHERS = 4.
IF sy-subrc NE 0.
server->response->set_status( code = 401 reason = 'Unauthorized' ).
RETURN.
ENDIF.
• OAuth 2.0: A secure protocol preferred for modern web services, especially for integration with external systems.
* Using SAP's OAuth library for OAuth integration
DATA: lo_oauth_client TYPE REF TO cl_oauth2_client,
lv_token TYPE string,
lv_valid TYPE abap_bool.
* Create OAuth client
CREATE OBJECT lo_oauth_client
EXPORTING
i_profile = 'ZOAUTH_PROFILE'. " OAuth configuration profile
* Get and validate access token
lv_token = server->request->get_header_field( 'Authorization' ).
lv_token = replace( val = lv_token sub = 'Bearer ' with = '' ).
lv_valid = lo_oauth_client->validate_token( lv_token ).
IF lv_valid EQ abap_false.
server->response->set_status( code = 401 reason = 'Unauthorized' ).
RETURN.
ENDIF.
2. Authorization Strategies
While authentication answers the question "who are you?", authorization answers "what can you do?":
• Service-Level Authorization: Permission control implemented through SAP Gateway authorization.
* Define service authorization object in /IWFND/GW_CLIENT transaction
* Then create roles for the relevant authorization object in PFCG transaction
* Authority check in SICF service handler
DATA: lo_server TYPE REF TO if_http_server,
ls_user_info TYPE usr02.
lo_server ?= server.
SELECT SINGLE * FROM usr02 INTO ls_user_info
WHERE bname = sy-uname.
* Service access control
AUTHORITY-CHECK OBJECT 'S_SERVICE'
ID 'SERVICE' FIELD 'ZODATA_SRV'
ID 'ACTVT' FIELD '16'. " 16 = Execute
IF sy-subrc NE 0.
lo_server->response->set_status( code = 403 reason = 'Forbidden' ).
RETURN.
ENDIF.
• Data Access Control: Implement data access controls to limit what data a user can view or modify.
* Data access control example (in DPC_EXT class)
METHOD materials_get_entityset.
" First fetch all data
SELECT * FROM mara INTO TABLE @DATA(lt_materials).
" Apply authority check
LOOP AT lt_materials ASSIGNING FIELD-SYMBOL(<fs_material>).
" Material group authority check
AUTHORITY-CHECK OBJECT 'M_MATE_MAT'
ID 'MATKL' FIELD <fs_material>-matkl
ID 'ACTVT' FIELD '03'. " 03 = Display
IF sy-subrc NE 0.
" User doesn't have permission to see this material, remove from list
DELETE lt_materials WHERE matnr = <fs_material>-matnr.
ENDIF.
ENDLOOP.
" Copy results to OData response
MOVE-CORRESPONDING lt_materials TO et_entityset.
ENDMETHOD.
• Operation-Level Authorization: Define specific authorization rules for CRUD (Create, Read, Update, Delete) operations.
* Authority check for CRUD operations (in DPC_EXT class)
METHOD check_authority_for_operation.
IMPORTING
iv_operation TYPE string
iv_entity_type TYPE string
RETURNING
rv_authorized TYPE abap_bool.
CASE iv_operation.
WHEN 'CREATE'.
AUTHORITY-CHECK OBJECT 'ZMATERIAL'
ID 'ACTVT' FIELD '01'. " 01 = Create
WHEN 'UPDATE'.
AUTHORITY-CHECK OBJECT 'ZMATERIAL'
ID 'ACTVT' FIELD '02'. " 02 = Change
WHEN 'DELETE'.
AUTHORITY-CHECK OBJECT 'ZMATERIAL'
ID 'ACTVT' FIELD '06'. " 06 = Delete
WHEN 'READ'.
AUTHORITY-CHECK OBJECT 'ZMATERIAL'
ID 'ACTVT' FIELD '03'. " 03 = Display
ENDCASE.
" Return response based on authority check result
IF sy-subrc = 0.
rv_authorized = abap_true.
ELSE.
rv_authorized = abap_false.
ENDIF.
ENDMETHOD.
Data Privacy and Protection
1. Data Leak Prevention
Transmitting only necessary data through OData services is important for both security and performance:
• Using $select: Encourage OData clients to select only the fields they need.
* Enable select in Entity Set metadata (in SEGW transaction)
lo_entity_set->set_selectable( abap_true ).
* Example client usage:
* /sap/opu/odata/SAP/ZODATA_SRV/MaterialSet?$select=Matnr,Maktx
• Limiting Data Scope: Avoid returning very large datasets.
* Limiting data scope in DPC_EXT class
METHOD limit_result_set.
IMPORTING
it_data TYPE ANY TABLE
iv_max_records TYPE i DEFAULT 1000
RETURNING
rt_limited_data TYPE STANDARD TABLE.
" Copy data not exceeding maximum record count
DATA(lv_count) = COND #( WHEN lines( it_data ) > iv_max_records
THEN iv_max_records
ELSE lines( it_data ) ).
rt_limited_data = VALUE #( FOR i = 1 UNTIL i > lv_count
( it_data[ i ] ) ).
ENDMETHOD.
2. Data Masking and Encryption
Use masking and encryption techniques to protect sensitive data:
• Sensitive Data Masking: Display masked credit card numbers, SSNs, and other sensitive information.
* Data masking example in DPC_EXT class
METHOD mask_sensitive_data.
IMPORTING
iv_value TYPE string
iv_type TYPE string
RETURNING
rv_masked TYPE string.
CASE iv_type.
WHEN 'CREDIT_CARD'.
" Mask credit card number except last 4 digits
rv_masked = COND #( WHEN strlen( iv_value ) > 4
THEN |XXXX-XXXX-XXXX-{ right( iv_value, 4 ) }|
ELSE iv_value ).
WHEN 'SSN'.
" Mask SSN's middle digits
rv_masked = COND #( WHEN strlen( iv_value ) = 9
THEN |{ substring( val = iv_value off = 0 len = 3 ) }-XX-{ right( iv_value, 4 ) }|
ELSE iv_value ).
WHEN OTHERS.
rv_masked = iv_value.
ENDCASE.
ENDMETHOD.
• Data Encryption: Use encryption for particularly privacy-sensitive data.
* Encrypting data using SAP Secure Store and Forward (SSF)
METHOD encrypt_sensitive_data.
IMPORTING
iv_data TYPE string
RETURNING
rv_encrypted TYPE string.
DATA: lv_envelope TYPE xstring,
lv_subject TYPE string,
lv_plain_data TYPE xstring.
" Convert data to binary format
CALL FUNCTION 'SCMS_STRING_TO_XSTRING'
EXPORTING
text = iv_data
IMPORTING
buffer = lv_plain_data
EXCEPTIONS
failed = 1
OTHERS = 2.
IF sy-subrc <> 0.
RETURN.
ENDIF.
" Encrypt the data
CALL FUNCTION 'SSF_KRN_ENVELOPE'
EXPORTING
ostr_input = lv_plain_data
IMPORTING
ostr_output = lv_envelope
EXCEPTIONS
ssf_krn_error = 1
ssf_krn_noop = 2
ssf_krn_nomemory = 3
ssf_krn_opinv = 4
ssf_krn_nossflib = 5
ssf_krn_recipient_error = 6
ssf_krn_input_data_error = 7
ssf_krn_invalid_par = 8
ssf_krn_invalid_parlen = 9
ssf_fb_input_parameter_error = 10
OTHERS = 11.
IF sy-subrc = 0.
" Base64 encode the encrypted data
CALL FUNCTION 'SCMS_XSTRING_TO_BASE64'
EXPORTING
buffer = lv_envelope
IMPORTING
output_string = rv_encrypted.
ENDIF.
ENDMETHOD.
Firewalls and Attack Prevention
1. OData Endpoint Security
Protect your OData endpoints with various security measures:
• API Rate Limiting: Implement request limiting to prevent DoS (Denial of Service) attacks.
* Simple request counter implementation (memory-based)
METHOD check_rate_limit.
IMPORTING
iv_user_id TYPE string
RETURNING
rv_allowed TYPE abap_bool.
DATA: lv_current_timestamp TYPE timestampl,
lv_max_requests TYPE i VALUE 100, " Maximum 100 requests in 10 minutes
lv_time_window TYPE i VALUE 600. " 10 minutes (in seconds)
GET TIME STAMP FIELD lv_current_timestamp.
" Check the user's request counter
SELECT COUNT(*) FROM zapi_request_log INTO @DATA(lv_request_count)
WHERE user_id = @iv_user_id
AND request_timestamp > @lv_current_timestamp - @lv_time_window.
" Reject if request count exceeds limit
IF lv_request_count >= lv_max_requests.
rv_allowed = abap_false.
ELSE.
" Log the request
INSERT INTO zapi_request_log VALUES @( VALUE #(
user_id = iv_user_id
request_timestamp = lv_current_timestamp
) ).
rv_allowed = abap_true.
ENDIF.
ENDMETHOD.
• Whitelist IP Filtering: Allow requests only from trusted IP addresses.
* IP check (in SICF handler)
METHOD check_allowed_ip.
IMPORTING
io_server TYPE REF TO if_http_server
RETURNING
rv_allowed TYPE abap_bool.
DATA: lv_ip_address TYPE string.
" Get client IP address
lv_ip_address = io_server->request->get_header_field( '~remote_addr' ).
" Check against allowed IP list
SELECT COUNT(*) FROM zallowed_ips INTO @DATA(lv_count)
WHERE ip_address = @lv_ip_address.
rv_allowed = COND #( WHEN lv_count > 0 THEN abap_true ELSE abap_false ).
ENDMETHOD.
2. Preventing Injection Attacks
Some of the most common attack vectors in OData services are injection attacks:
• SQL Injection Prevention: Prevent SQL injection by using parameterized queries.
* SQL Injection Prevention Methods
* 1. Always use parameterized queries:
" WRONG - vulnerable to SQL injection:
* DATA(lv_where) = |MATNR = '{ iv_matnr }'|.
* SELECT * FROM mara WHERE (lv_where) INTO TABLE @lt_result.
" RIGHT - parameterized query:
DATA(lv_matnr) = iv_matnr.
SELECT * FROM mara WHERE matnr = @lv_matnr INTO TABLE @lt_result.
* 2. Securely building dynamic WHERE conditions for SELECT:
DATA: lt_where_clauses TYPE STANDARD TABLE OF string,
lv_where TYPE string.
" Build dynamic filter conditions safely
IF iv_matnr IS NOT INITIAL.
APPEND |MATNR = @LV_MATNR| TO lt_where_clauses.
ENDIF.
IF iv_matkl IS NOT INITIAL.
APPEND |MATKL = @LV_MATKL| TO lt_where_clauses.
ENDIF.
" Combine WHERE conditions
IF lt_where_clauses IS NOT INITIAL.
lv_where = CONCAT_LINES_OF( table = lt_where_clauses sep = | AND | ).
" Always create query with parameter references
SELECT * FROM mara WHERE (lv_where) INTO TABLE @lt_result.
ENDIF.
• XSS (Cross-Site Scripting) Prevention: Properly validate and sanitize user inputs.
* Sanitizing user input against XSS attacks
METHOD sanitize_user_input.
IMPORTING
iv_input TYPE string
RETURNING
rv_sanitized TYPE string.
" Convert HTML special characters
rv_sanitized = iv_input.
" Convert < and > characters
REPLACE ALL OCCURRENCES OF '<' IN rv_sanitized WITH '<'.
REPLACE ALL OCCURRENCES OF '>' IN rv_sanitized WITH '>'.
" Convert quote characters
REPLACE ALL OCCURRENCES OF '"' IN rv_sanitized WITH '"'.
REPLACE ALL OCCURRENCES OF '''' IN rv_sanitized WITH '''.
" Remove potentially harmful script tags
REPLACE ALL OCCURRENCES OF REGEX '.*?' IN rv_sanitized WITH '' IGNORING CASE.
REPLACE ALL OCCURRENCES OF REGEX 'javascript:' IN rv_sanitized WITH '' IGNORING CASE.
REPLACE ALL OCCURRENCES OF REGEX 'onerror=' IN rv_sanitized WITH '' IGNORING CASE.
RETURN rv_sanitized.
ENDMETHOD.
Error Management and Secure Feedback
1. Secure Error Handling
Error messages can expose vulnerabilities, so they must be handled carefully:
• Hiding Sensitive Information: Avoid sharing sensitive information in error messages shown to users.
* Secure error handling in DPC_EXT class
METHOD handle_exception_safely.
IMPORTING
ix_exception TYPE REF TO cx_root
io_response TYPE REF TO /iwbep/if_mgw_response
RETURNING
rv_handled TYPE abap_bool.
DATA: lv_message TYPE string,
lv_tech_message TYPE string.
" Get technical error message
lv_tech_message = ix_exception->get_text( ).
" Log technical details to error log (for debugging)
log_error_to_database(
iv_error_text = lv_tech_message
iv_user = sy-uname
iv_timestamp = sy-datum && sy-uzeit
).
" Create appropriate and secure message for user
CASE TYPE OF ix_exception.
WHEN cx_sy_open_sql_db.
" Database error - don't show technical details
lv_message = 'A database access error occurred. Please try again later.'.
WHEN cx_sy_authorization_error.
" Authorization error
lv_message = 'You do not have permission for this operation.'.
WHEN OTHERS.
" Generic error message
lv_message = 'An unexpected error occurred during the operation. Please contact your system administrator for assistance.'.
ENDCASE.
" Add error message to response
io_response->set_message_text( lv_message ).
" Set appropriate HTTP status code
CASE TYPE OF ix_exception.
WHEN cx_sy_authorization_error.
io_response->set_status_code( /iwbep/if_mgw_core_types=>gcs_http_status_codes-forbidden ). " 403
WHEN cx_sy_open_sql_db.
io_response->set_status_code( /iwbep/if_mgw_core_types=>gcs_http_status_codes-internal_server_error ). " 500
WHEN OTHERS.
io_response->set_status_code( /iwbep/if_mgw_core_types=>gcs_http_status_codes-bad_request ). " 400
ENDCASE.
" Error has been handled
rv_handled = abap_true.
ENDMETHOD.
2. Security Incident Monitoring
Continuously monitor the security status of your OData services:
• Audit Logging: Log all OData accesses and security incidents.
* Creating OData access audit log
METHOD log_odata_access.
IMPORTING
iv_entity_name TYPE string
iv_operation TYPE string
iv_user TYPE syuname
iv_source_ip TYPE string
iv_success TYPE abap_bool
iv_error_message TYPE string OPTIONAL.
" Create log record
DATA: ls_log TYPE zodata_access_log.
ls_log-entity_name = iv_entity_name.
ls_log-operation = iv_operation.
ls_log-user_id = iv_user.
ls_log-source_ip = iv_source_ip.
ls_log-access_time = sy-datum && sy-uzeit.
ls_log-success_flag = iv_success.
ls_log-error_message = iv_error_message.
" Add log record to database
INSERT zodata_access_log FROM ls_log.
ENDMETHOD.
• Abnormal Behavior Detection: Detect suspicious or abnormal access patterns and generate alerts.
* Suspicious access detection
METHOD detect_suspicious_activity.
IMPORTING
iv_user TYPE syuname
RETURNING
rv_suspicious TYPE abap_bool.
DATA: lv_current_time TYPE tzntstmpl,
lv_threshold TYPE i VALUE 50, " More than 50 requests in 5 minutes is suspicious
lv_timeframe TYPE i VALUE 300. " 5 minutes (in seconds)
GET TIME STAMP FIELD lv_current_time.
" Check request count in the last 5 minutes
SELECT COUNT(*) FROM zodata_access_log INTO @DATA(lv_request_count)
WHERE user_id = @iv_user
AND access_time > @lv_current_time - @lv_timeframe.
" Check failed login attempts
SELECT COUNT(*) FROM zodata_access_log INTO @DATA(lv_failed_count)
WHERE user_id = @iv_user
AND access_time > @lv_current_time - @lv_timeframe
AND success_flag = @abap_false.
" Check access from different IP addresses
SELECT COUNT( DISTINCT source_ip ) FROM zodata_access_log
INTO @DATA(lv_ip_count)
WHERE user_id = @iv_user
AND access_time > @lv_current_time - @lv_timeframe.
" Check for suspicious activity
rv_suspicious = COND #( WHEN lv_request_count > lv_threshold
OR lv_failed_count > 5
OR lv_ip_count > 3
THEN abap_true
ELSE abap_false ).
" Send notification when suspicious activity is detected
IF rv_suspicious = abap_true.
send_security_alert(
iv_alert_type = 'SUSPICIOUS_ACTIVITY'
iv_user = iv_user
iv_details = |{ lv_request_count } requests, { lv_failed_count } failed, { lv_ip_count } different IPs|
).
ENDIF.
ENDMETHOD.
Application-Level Security
1. Communication Security
Protect the communication between your OData services and clients:
• HTTPS Requirement: Mandate the use of HTTPS for all OData traffic.
* HTTPS requirement in SICF
IF server->request->get_header_field( '~server_protocol' ) NE 'HTTPS'.
" Reject non-HTTPS requests
server->response->set_status( code = 403 reason = 'HTTPS Required' ).
server->response->set_cdata( 'This service is only accessible via HTTPS.' ).
RETURN.
ENDIF.
• HTTP Security Headers: Configure HTTP security headers for additional security.
* Setting security headers
METHOD set_security_headers.
IMPORTING
io_response TYPE REF TO if_http_response.
" Content Security Policy (CSP) - helps prevent XSS attacks
io_response->set_header_field( name = 'Content-Security-Policy'
value = 'default-src ''self''' ).
" X-XSS-Protection - enables XSS protection in some browsers
io_response->set_header_field( name = 'X-XSS-Protection'
value = '1; mode=block' ).
" X-Content-Type-Options - prevents MIME type misuse
io_response->set_header_field( name = 'X-Content-Type-Options'
value = 'nosniff' ).
" Referrer-Policy - controls which referrer information is sent in external links
io_response->set_header_field( name = 'Referrer-Policy'
value = 'same-origin' ).
" Strict-Transport-Security - forces HTTPS usage
io_response->set_header_field( name = 'Strict-Transport-Security'
value = 'max-age=31536000; includeSubDomains' ).
ENDMETHOD.
2. Security Implementation and Improvement
Continuously improve the security posture of your OData services:
• Security Testing: Regularly perform penetration tests and vulnerability scans.
* Security testing checklist:
* 1. Using open-source security scanners for OData services
* 2. Authorization bypass tests
* 3. SQL injection and XSS tests
* 4. DoS attack resilience tests
* 5. Authentication security tests
* Example test scenario code (for automation):
METHOD run_security_tests.
DATA: lt_test_results TYPE STANDARD TABLE OF zsecurity_test_result.
" Authorization bypass test
lt_test_results = VALUE #( BASE lt_test_results
( test_id = 'AUTH-001'
test_name = 'Entity access without role'
result = test_entity_access_without_role( )
timestamp = sy-datum && sy-uzeit ) ).
" Filter bypass test
lt_test_results = VALUE #( BASE lt_test_results
( test_id = 'FILTER-001'
test_name = 'SQL Injection Filter'
result = test_sql_injection_filter( )
timestamp = sy-datum && sy-uzeit ) ).
" Save test results
MODIFY zsecurity_test_log FROM TABLE lt_test_results.
" Generate alert
DATA(lv_failed_tests) = REDUCE i( INIT x = 0 FOR wa IN lt_test_results
WHERE ( result = abap_false ) NEXT x = x + 1 ).
IF lv_failed_tests > 0.
send_security_alert(
iv_alert_type = 'SECURITY_TEST_FAILED'
iv_details = |{ lv_failed_tests } security tests failed|
).
ENDIF.
ENDMETHOD.
• Security Updates: Keep your OData components and dependencies up to date.
* SAP Note check function
METHOD check_sap_security_notes.
IMPORTING
iv_component TYPE string
RETURNING
rt_missing_notes TYPE STANDARD TABLE OF string.
DATA: lt_implemented_notes TYPE STANDARD TABLE OF string,
lt_required_notes TYPE STANDARD TABLE OF string.
" Check SAP Notes implemented in your system
CALL FUNCTION 'SNOTE_GET_IMPLEMENTED_NOTES'
TABLES
notetab = lt_implemented_notes.
" Get required security notes for this component (example)
CASE iv_component.
WHEN 'ODATA'.
lt_required_notes = VALUE #( ( '1234567' ) ( '2345678' ) ( '3456789' ) ).
WHEN 'GATEWAY'.
lt_required_notes = VALUE #( ( '2345678' ) ( '3456789' ) ( '4567890' ) ).
WHEN OTHERS.
" Default security notes
lt_required_notes = VALUE #( ( '1111111' ) ( '2222222' ) ).
ENDCASE.
" Determine missing notes
LOOP AT lt_required_notes INTO DATA(lv_required_note).
READ TABLE lt_implemented_notes TRANSPORTING NO FIELDS
WITH KEY table_line = lv_required_note.
IF sy-subrc <> 0.
" Note not implemented
APPEND lv_required_note TO rt_missing_notes.
ENDIF.
ENDLOOP.
ENDMETHOD.
Conclusion
Implementing security and authorization policies in SAP OData services is critical to ensuring your data security and preventing unauthorized access. Using the strategies and code examples covered in this guide, you can protect your OData services with strong authentication, robust authorization, data protection, and attack prevention mechanisms.
By implementing these security measures, you can provide secure, reliable, and performant OData services for both internal users and external consumers.
As with any security strategy, no single measure is sufficient. By adopting a defense-in-depth approach and implementing multiple layers of security, you can significantly enhance the security of your SAP OData services.
Comments
No comments yet.
Be the first to comment.



