diff --git a/doc/commands/strust.md b/doc/commands/strust.md index f140d285..79e060eb 100644 --- a/doc/commands/strust.md +++ b/doc/commands/strust.md @@ -71,13 +71,14 @@ Creates a new (or replaces an existing) STRUST Identity. The description will be language settings. ```bash -sapcli strust createidentity [-i|--identity IDENTITY] [-s|--storage STORAGE] [-d|--description DESCRIPTION] [--overwrite] +sapcli strust createidentity [-i|--identity IDENTITY] [-s|--storage STORAGE] [-d|--description DESCRIPTION] [-l|--language-iso-code LANG-ISO-639] [--overwrite] ``` **Parameters**: - `--identity`: STRUST identity (PSE context + PSE application). **(Mutually exclusive with the option --storage)** - `--storage`: Predefined STRUST identities. **(Mutually exclusive with the option --identity)** - `--description`: Identity Description. **(optional)** +- `--language-iso-code`: Language of Identity Description - if not given, the language will be deduced from the current system locale. **(optional)** - `--overwrite`: Overwrite the existing Identity, default is `False`. **(optional)** # getcsr diff --git a/sap/cli/strust.py b/sap/cli/strust.py index d333544f..6f23db50 100644 --- a/sap/cli/strust.py +++ b/sap/cli/strust.py @@ -74,7 +74,8 @@ def _get_ssl_storage_from_args(connection, args): connection, identity.pse_context, identity.pse_applic, - description=getattr(args, 'description', None) + description=getattr(args, 'description', None), + lang_iso_code=getattr(args, 'language_iso_code', None) ) @@ -144,6 +145,8 @@ def createpse(connection, args): return 0 +@CommandGroup.argument('-l', '--language-iso-code', type=str, + help='ISO code of language of Identity description (default: the current system locale') @CommandGroup.argument('-d', '--description', type=str, help='Identity description') @CommandGroup.argument('--overwrite', help='Overwrite the existing STRUST Identity', action='store_true', default=False) @CommandGroup.argument('-s', '--storage', default=None, help='Mutually exclusive with the option -i', diff --git a/sap/platform/language.py b/sap/platform/language.py index 9decd429..f3ee696d 100644 --- a/sap/platform/language.py +++ b/sap/platform/language.py @@ -1,5 +1,7 @@ """ABAP Platform helpers and utilities""" +from locale import getlocale + from sap.errors import SAPCliError # Supported Languages and Code Pages (Non-Unicode) @@ -66,8 +68,27 @@ def sap_code_to_iso_code(sap_code: str) -> str: def iso_code_to_sap_code(iso_code: str) -> str: """Coverts ISO codes to one letter SAP language codes""" + iso_code = iso_code.upper() try: return next((entry[1] for entry in CODE_LIST if entry[0] == iso_code)) except StopIteration: # pylint: disable=raise-missing-from raise SAPCliError(f'Not found ISO Code: {iso_code}') + + +def locale_lang_sap_code() -> str: + """Reads current system locale and attempts to convert the language part + to SAP Language Code + """ + + loc = getlocale() + lang = loc[0] or "" + + if len(lang) < 2: + raise SAPCliError(f'The current system locale language is not ISO 3166: {lang}') + + try: + return iso_code_to_sap_code(lang[0:2].upper()) + except SAPCliError as ex: + raise SAPCliError( + f'The current system locale language cannot be converted to SAP language code: {lang}') from ex diff --git a/sap/rfc/strust.py b/sap/rfc/strust.py index 508e4022..c3c48eca 100644 --- a/sap/rfc/strust.py +++ b/sap/rfc/strust.py @@ -1,6 +1,9 @@ """SAP STRUST utilities""" -from sap.platform.language import iso_code_to_sap_code +from sap.platform.language import ( + iso_code_to_sap_code, + locale_lang_sap_code +) from sap.rfc.bapi import ( BAPIReturn, BAPIError @@ -121,12 +124,15 @@ def __init__(self, connection, pse_context, pse_applic, description=None, lang_i 'PSE_APPLIC': pse_applic } - self.description = { - 'PSE_DESCRIPT': description - } + self.description = {} + + if description is not None: + self.description['PSE_DESCRIPT'] = description - if lang_iso_code: - self.description['SPRSL'] = iso_code_to_sap_code(lang_iso_code) + if lang_iso_code: + self.description['SPRSL'] = iso_code_to_sap_code(lang_iso_code) + else: + self.description['SPRSL'] = locale_lang_sap_code() def __repr__(self): return 'SSL Storage {PSE_CONTEXT}/{PSE_APPLIC}'.format(**self.identity) diff --git a/test/unit/test_sap_cli_strust.py b/test/unit/test_sap_cli_strust.py index 58e24671..e7ad86d9 100644 --- a/test/unit/test_sap_cli_strust.py +++ b/test/unit/test_sap_cli_strust.py @@ -1049,7 +1049,10 @@ def createidentity(self, *test_args): def test_createidentity_with_storage_ok(self): self.mock_connection.call.return_value = {'ET_BAPIRET2':[]} - self.createidentity('-s', 'server_standard', '--description', 'Identity Description') + self.createidentity( + '-s', 'server_standard', + '--description', 'Identity Description', + '-l', 'zh') self.mock_connection.call.assert_called_once_with( 'SSFR_IDENTITY_CREATE', @@ -1057,6 +1060,7 @@ def test_createidentity_with_storage_ok(self): 'PSE_CONTEXT': 'SSLS', 'PSE_APPLIC': 'DFAULT', 'PSE_DESCRIPT': 'Identity Description', + 'SPRSL': '1', }, IV_REPLACE_EXISTING_APPL='-', ) @@ -1064,7 +1068,10 @@ def test_createidentity_with_storage_ok(self): def test_createidentity_with_identity_ok(self): self.mock_connection.call.return_value = {'ET_BAPIRET2':[]} - self.createidentity('-i', 'SSLC/100_SD', '--description', 'Identity Description') + self.createidentity( + '-i', 'SSLC/100_SD', + '--description', 'Identity Description', + '-l', 'zh') self.mock_connection.call.assert_called_once_with( 'SSFR_IDENTITY_CREATE', @@ -1072,6 +1079,7 @@ def test_createidentity_with_identity_ok(self): 'PSE_CONTEXT': 'SSLC', 'PSE_APPLIC': '100_SD', 'PSE_DESCRIPT': 'Identity Description', + 'SPRSL': '1', }, IV_REPLACE_EXISTING_APPL='-', ) @@ -1079,7 +1087,11 @@ def test_createidentity_with_identity_ok(self): def test_createidentity_with_replace_ok(self): self.mock_connection.call.return_value = {'ET_BAPIRET2':[]} - self.createidentity('-s', 'server_standard', '--description', 'Identity Description', '--overwrite') + self.createidentity( + '-s', 'server_standard', + '--description', 'Identity Description', + '--language-iso-code', 'zh', + '--overwrite') self.mock_connection.call.assert_called_once_with( 'SSFR_IDENTITY_CREATE', @@ -1087,10 +1099,29 @@ def test_createidentity_with_replace_ok(self): 'PSE_CONTEXT': 'SSLS', 'PSE_APPLIC': 'DFAULT', 'PSE_DESCRIPT': 'Identity Description', + 'SPRSL': '1', }, IV_REPLACE_EXISTING_APPL='X', ) + def test_createidentity_with_language_from_locale(self): + self.mock_connection.call.return_value = {'ET_BAPIRET2':[]} + + with patch('sap.platform.language.getlocale', return_value=('zh_CN', 'UTF-8')): + self.createidentity( + '-s', 'server_standard', + '--description', 'Identity Description') + + self.mock_connection.call.assert_called_once_with( + 'SSFR_IDENTITY_CREATE', + IS_STRUST_IDENTITY={ + 'PSE_CONTEXT': 'SSLS', + 'PSE_APPLIC': 'DFAULT', + 'PSE_DESCRIPT': 'Identity Description', + 'SPRSL': '1', + }, + IV_REPLACE_EXISTING_APPL='-', + ) if __name__ == '__main__': unittest.main() diff --git a/test/unit/test_sap_platform_language.py b/test/unit/test_sap_platform_language.py index e854baff..bbea918f 100644 --- a/test/unit/test_sap_platform_language.py +++ b/test/unit/test_sap_platform_language.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import unittest +from unittest.mock import patch import sap.errors import sap.platform.language @@ -20,6 +21,9 @@ def test_sap_code_to_iso_code_not_found(self): def test_iso_code_to_sap_code_ok(self): self.assertEqual(sap.platform.language.iso_code_to_sap_code('EN'), 'E') + def test_iso_code_to_sap_code_lower_ok(self): + self.assertEqual(sap.platform.language.iso_code_to_sap_code('en'), 'E') + def test_iso_code_to_sap_code_not_found(self): with self.assertRaises(sap.errors.SAPCliError) as raised: sap.platform.language.iso_code_to_sap_code('#') @@ -27,5 +31,40 @@ def test_iso_code_to_sap_code_not_found(self): self.assertEqual(str(raised.exception), 'Not found ISO Code: #') +class TestSAPPlatformLanguageLocale(unittest.TestCase): + + def test_locale_lang_to_sap_code_ok(self): + with patch('sap.platform.language.getlocale', return_value=('en_US', 'UTF-8')): + sap_lang = sap.platform.language.locale_lang_sap_code() + + self.assertEqual(sap_lang, 'E') + + def test_locale_lang_to_sap_code_C(self): + """This is a special case for a short case because C is kinda special + as it is the POSIX portable locale and we may want to handle it + differently in feature. + """ + + with patch('sap.platform.language.getlocale', return_value=('C', 'UTF-8')): + with self.assertRaises(sap.errors.SAPCliError) as caught: + sap_lang = sap.platform.language.locale_lang_sap_code() + + self.assertEqual(str(caught.exception), 'The current system locale language is not ISO 3166: C') + + def test_locale_lang_to_sap_code_short(self): + with patch('sap.platform.language.getlocale', return_value=('e', 'UTF-8')): + with self.assertRaises(sap.errors.SAPCliError) as caught: + sap_lang = sap.platform.language.locale_lang_sap_code() + + self.assertEqual(str(caught.exception), 'The current system locale language is not ISO 3166: e') + + def test_locale_lang_to_sap_code_unknown(self): + with patch('sap.platform.language.getlocale', return_value=('WF', 'UTF-8')): + with self.assertRaises(sap.errors.SAPCliError) as caught: + sap_lang = sap.platform.language.locale_lang_sap_code() + + self.assertEqual(str(caught.exception), 'The current system locale language cannot be converted to SAP language code: WF') + + if __name__ == '__main__': unittest.main() diff --git a/test/unit/test_sap_rfc_strust.py b/test/unit/test_sap_rfc_strust.py index 55835645..47e5f5c9 100644 --- a/test/unit/test_sap_rfc_strust.py +++ b/test/unit/test_sap_rfc_strust.py @@ -105,8 +105,7 @@ def test_ctor(self): self.assertEqual(self.ssl_storage.identity['PSE_CONTEXT'], self.pse_context) self.assertEqual(self.ssl_storage.identity['PSE_APPLIC'], self.pse_applic) - self.assertEqual(self.ssl_storage.description['PSE_DESCRIPT'], None) - self.assertEqual(len(self.ssl_storage.description), 1) + self.assertEqual(len(self.ssl_storage.description), 0) def test_ctor_with_description_and_lang(self): self.assertIs(self.ssl_storage._connection, self.connection) @@ -115,6 +114,17 @@ def test_ctor_with_description_and_lang(self): self.assertEqual(self.ssl_storage.description['PSE_DESCRIPT'], self.pse_description) self.assertEqual(self.ssl_storage.description['SPRSL'], '1') + def test_ctor_with_description_and_lang_from_locale(self): + with patch('sap.rfc.strust.locale_lang_sap_code', return_value='1') as fake_locale_lang_sap_code: + self.ssl_storage = SSLCertStorage( + self.connection, + self.pse_context, + self.pse_applic, + description=self.pse_description) + + fake_locale_lang_sap_code.assert_called_once_with() + self.assertEqual(self.ssl_storage.description['SPRSL'], '1') + def test_repr(self): self.assertEqual(repr(self.ssl_storage), f'SSL Storage {self.pse_context}/{self.pse_applic}')