Allow keycloak modules to take token as parameter for the auth. (#2250)
* Allow keycloak_group.py to take token as parameter for the authentification * Fix some pep8 issues * Add changelog fragment * Refactor get_token to pass module.params + Documentation * Update plugins/module_utils/identity/keycloak/keycloak.py Co-authored-by: Felix Fontein <felix@fontein.de> * Update plugins/module_utils/identity/keycloak/keycloak.py Co-authored-by: Felix Fontein <felix@fontein.de> * Fix unit test and add new one for token as param * Fix identation * Check base_url format also if token is given * Update plugins/doc_fragments/keycloak.py Co-authored-by: Felix Fontein <felix@fontein.de> * Update plugins/modules/identity/keycloak/keycloak_client.py Co-authored-by: Felix Fontein <felix@fontein.de> * Update plugins/modules/identity/keycloak/keycloak_clienttemplate.py Co-authored-by: Felix Fontein <felix@fontein.de> * Allow keycloak_group.py to take token as parameter for the authentification * Refactor get_token to pass module.params + Documentation * Update plugins/module_utils/identity/keycloak/keycloak.py Co-authored-by: Felix Fontein <felix@fontein.de> * Update plugins/modules/identity/keycloak/keycloak_group.py Co-authored-by: Felix Fontein <felix@fontein.de> * Check if base_url is None before to check format * Fix unit test: rename base_url parameter to auth_keycloak_url * Update plugins/module_utils/identity/keycloak/keycloak.py Co-authored-by: Felix Fontein <felix@fontein.de> * Update changelogs/fragments/2250-allow-keycloak-modules-to-take-token-as-param.yml Co-authored-by: Amin Vakil <info@aminvakil.com> * Update plugins/module_utils/identity/keycloak/keycloak.py Co-authored-by: Amin Vakil <info@aminvakil.com> * Update plugins/modules/identity/keycloak/keycloak_client.py Co-authored-by: Amin Vakil <info@aminvakil.com> * Update plugins/modules/identity/keycloak/keycloak_client.py Co-authored-by: Amin Vakil <info@aminvakil.com> * Update plugins/modules/identity/keycloak/keycloak_clienttemplate.py Co-authored-by: Amin Vakil <info@aminvakil.com> * Update changelogs/fragments/2250-allow-keycloak-modules-to-take-token-as-param.yml Co-authored-by: Amin Vakil <info@aminvakil.com> * Update plugins/module_utils/identity/keycloak/keycloak.py Co-authored-by: Amin Vakil <info@aminvakil.com> * Update plugins/modules/identity/keycloak/keycloak_clienttemplate.py Co-authored-by: Amin Vakil <info@aminvakil.com> * Update plugins/modules/identity/keycloak/keycloak_group.py Co-authored-by: Amin Vakil <info@aminvakil.com> * Update plugins/modules/identity/keycloak/keycloak_group.py Co-authored-by: Amin Vakil <info@aminvakil.com> * Switch to modern syntax for the documentation (e.g. community.general.keycloak_client) * Add check either creds or token as argument of all keyloak_* modules * Update plugins/modules/identity/keycloak/keycloak_client.py Co-authored-by: Felix Fontein <felix@fontein.de> Co-authored-by: Amin Vakil <info@aminvakil.com>pull/2286/head
parent
5b4fab80e2
commit
6ab9b05da3
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
minor_changes:
|
||||
- keycloak_* modules - allow the keycloak modules to use a token for the
|
||||
authentication, the modules can take either a token or the credentials
|
||||
(https://github.com/ansible-collections/community.general/pull/2250).
|
|
@ -30,7 +30,6 @@ options:
|
|||
description:
|
||||
- Keycloak realm name to authenticate to for API access.
|
||||
type: str
|
||||
required: true
|
||||
|
||||
auth_client_secret:
|
||||
description:
|
||||
|
@ -41,7 +40,6 @@ options:
|
|||
description:
|
||||
- Username to authenticate for API access with.
|
||||
type: str
|
||||
required: true
|
||||
aliases:
|
||||
- username
|
||||
|
||||
|
@ -49,10 +47,15 @@ options:
|
|||
description:
|
||||
- Password to authenticate for API access with.
|
||||
type: str
|
||||
required: true
|
||||
aliases:
|
||||
- password
|
||||
|
||||
token:
|
||||
description:
|
||||
- Authentication token for Keycloak API.
|
||||
type: str
|
||||
version_added: 3.0.0
|
||||
|
||||
validate_certs:
|
||||
description:
|
||||
- Verify TLS certificates (do not disable this in production).
|
||||
|
|
|
@ -57,11 +57,12 @@ def keycloak_argument_spec():
|
|||
return dict(
|
||||
auth_keycloak_url=dict(type='str', aliases=['url'], required=True, no_log=False),
|
||||
auth_client_id=dict(type='str', default='admin-cli'),
|
||||
auth_realm=dict(type='str', required=True),
|
||||
auth_realm=dict(type='str'),
|
||||
auth_client_secret=dict(type='str', default=None, no_log=True),
|
||||
auth_username=dict(type='str', aliases=['username'], required=True),
|
||||
auth_password=dict(type='str', aliases=['password'], required=True, no_log=True),
|
||||
validate_certs=dict(type='bool', default=True)
|
||||
auth_username=dict(type='str', aliases=['username']),
|
||||
auth_password=dict(type='str', aliases=['password'], no_log=True),
|
||||
validate_certs=dict(type='bool', default=True),
|
||||
token=dict(type='str', no_log=True),
|
||||
)
|
||||
|
||||
|
||||
|
@ -73,10 +74,26 @@ class KeycloakError(Exception):
|
|||
pass
|
||||
|
||||
|
||||
def get_token(base_url, validate_certs, auth_realm, client_id,
|
||||
auth_username, auth_password, client_secret):
|
||||
def get_token(module_params):
|
||||
""" Obtains connection header with token for the authentication,
|
||||
token already given or obtained from credentials
|
||||
:param module_params: parameters of the module
|
||||
:return: connection header
|
||||
"""
|
||||
token = module_params.get('token')
|
||||
base_url = module_params.get('auth_keycloak_url')
|
||||
|
||||
if not base_url.lower().startswith(('http', 'https')):
|
||||
raise KeycloakError("auth_url '%s' should either start with 'http' or 'https'." % base_url)
|
||||
|
||||
if token is None:
|
||||
base_url = module_params.get('auth_keycloak_url')
|
||||
validate_certs = module_params.get('validate_certs')
|
||||
auth_realm = module_params.get('auth_realm')
|
||||
client_id = module_params.get('auth_client_id')
|
||||
auth_username = module_params.get('auth_username')
|
||||
auth_password = module_params.get('auth_password')
|
||||
client_secret = module_params.get('auth_client_secret')
|
||||
auth_url = URL_TOKEN.format(url=base_url, realm=auth_realm)
|
||||
temp_payload = {
|
||||
'grant_type': 'password',
|
||||
|
@ -101,13 +118,14 @@ def get_token(base_url, validate_certs, auth_realm, client_id,
|
|||
% (auth_url, str(e)))
|
||||
|
||||
try:
|
||||
return {
|
||||
'Authorization': 'Bearer ' + r['access_token'],
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
token = r['access_token']
|
||||
except KeyError:
|
||||
raise KeycloakError(
|
||||
'Could not obtain access token from %s' % auth_url)
|
||||
return {
|
||||
'Authorization': 'Bearer ' + token,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
|
||||
class KeycloakAPI(object):
|
||||
|
|
|
@ -511,20 +511,30 @@ author:
|
|||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Create or update Keycloak client (minimal example)
|
||||
local_action:
|
||||
module: keycloak_client
|
||||
auth_client_id: admin-cli
|
||||
- name: Create or update Keycloak client (minimal example), authentication with credentials
|
||||
community.general.keycloak_client:
|
||||
auth_keycloak_url: https://auth.example.com/auth
|
||||
auth_realm: master
|
||||
auth_username: USERNAME
|
||||
auth_password: PASSWORD
|
||||
client_id: test
|
||||
state: present
|
||||
delegate_to: localhost
|
||||
|
||||
|
||||
- name: Create or update Keycloak client (minimal example), authentication with token
|
||||
community.general.keycloak_client:
|
||||
auth_client_id: admin-cli
|
||||
auth_keycloak_url: https://auth.example.com/auth
|
||||
auth_realm: master
|
||||
token: TOKEN
|
||||
client_id: test
|
||||
state: present
|
||||
delegate_to: localhost
|
||||
|
||||
|
||||
- name: Delete a Keycloak client
|
||||
local_action:
|
||||
module: keycloak_client
|
||||
community.general.keycloak_client:
|
||||
auth_client_id: admin-cli
|
||||
auth_keycloak_url: https://auth.example.com/auth
|
||||
auth_realm: master
|
||||
|
@ -532,10 +542,11 @@ EXAMPLES = '''
|
|||
auth_password: PASSWORD
|
||||
client_id: test
|
||||
state: absent
|
||||
delegate_to: localhost
|
||||
|
||||
|
||||
- name: Create or update a Keycloak client (with all the bells and whistles)
|
||||
local_action:
|
||||
module: keycloak_client
|
||||
community.general.keycloak_client:
|
||||
auth_client_id: admin-cli
|
||||
auth_keycloak_url: https://auth.example.com/auth
|
||||
auth_realm: master
|
||||
|
@ -619,6 +630,7 @@ EXAMPLES = '''
|
|||
use.jwks.url: true
|
||||
jwks.url: JWKS_URL_FOR_CLIENT_AUTH_JWT
|
||||
jwt.credential.certificate: JWT_CREDENTIAL_CERTIFICATE_FOR_CLIENT_AUTH
|
||||
delegate_to: localhost
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
|
@ -740,21 +752,15 @@ def main():
|
|||
|
||||
module = AnsibleModule(argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
required_one_of=([['client_id', 'id']]))
|
||||
required_one_of=([['client_id', 'id'],
|
||||
['token', 'auth_realm', 'auth_username', 'auth_password']]),
|
||||
required_together=([['auth_realm', 'auth_username', 'auth_password']]))
|
||||
|
||||
result = dict(changed=False, msg='', diff={}, proposed={}, existing={}, end_state={})
|
||||
|
||||
# Obtain access token, initialize API
|
||||
try:
|
||||
connection_header = get_token(
|
||||
base_url=module.params.get('auth_keycloak_url'),
|
||||
validate_certs=module.params.get('validate_certs'),
|
||||
auth_realm=module.params.get('auth_realm'),
|
||||
client_id=module.params.get('auth_client_id'),
|
||||
auth_username=module.params.get('auth_username'),
|
||||
auth_password=module.params.get('auth_password'),
|
||||
client_secret=module.params.get('auth_client_secret'),
|
||||
)
|
||||
connection_header = get_token(module.params)
|
||||
except KeycloakError as e:
|
||||
module.fail_json(msg=str(e))
|
||||
|
||||
|
|
|
@ -169,9 +169,8 @@ author:
|
|||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Create or update Keycloak client template (minimal)
|
||||
local_action:
|
||||
module: keycloak_clienttemplate
|
||||
- name: Create or update Keycloak client template (minimal), authentication with credentials
|
||||
community.general.keycloak_client:
|
||||
auth_client_id: admin-cli
|
||||
auth_keycloak_url: https://auth.example.com/auth
|
||||
auth_realm: master
|
||||
|
@ -179,10 +178,20 @@ EXAMPLES = '''
|
|||
auth_password: PASSWORD
|
||||
realm: master
|
||||
name: this_is_a_test
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Create or update Keycloak client template (minimal), authentication with token
|
||||
community.general.keycloak_clienttemplate:
|
||||
auth_client_id: admin-cli
|
||||
auth_keycloak_url: https://auth.example.com/auth
|
||||
auth_realm: master
|
||||
token: TOKEN
|
||||
realm: master
|
||||
name: this_is_a_test
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Delete Keycloak client template
|
||||
local_action:
|
||||
module: keycloak_clienttemplate
|
||||
community.general.keycloak_client:
|
||||
auth_client_id: admin-cli
|
||||
auth_keycloak_url: https://auth.example.com/auth
|
||||
auth_realm: master
|
||||
|
@ -191,10 +200,10 @@ EXAMPLES = '''
|
|||
realm: master
|
||||
state: absent
|
||||
name: test01
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Create or update Keycloak client template (with a protocol mapper)
|
||||
local_action:
|
||||
module: keycloak_clienttemplate
|
||||
community.general.keycloak_client:
|
||||
auth_client_id: admin-cli
|
||||
auth_keycloak_url: https://auth.example.com/auth
|
||||
auth_realm: master
|
||||
|
@ -217,6 +226,7 @@ EXAMPLES = '''
|
|||
protocolMapper: oidc-usermodel-property-mapper
|
||||
full_scope_allowed: false
|
||||
id: bce6f5e9-d7d3-4955-817e-c5b7f8d65b3f
|
||||
delegate_to: localhost
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
|
@ -296,21 +306,15 @@ def main():
|
|||
|
||||
module = AnsibleModule(argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
required_one_of=([['id', 'name']]))
|
||||
required_one_of=([['id', 'name'],
|
||||
['token', 'auth_realm', 'auth_username', 'auth_password']]),
|
||||
required_together=([['auth_realm', 'auth_username', 'auth_password']]))
|
||||
|
||||
result = dict(changed=False, msg='', diff={}, proposed={}, existing={}, end_state={})
|
||||
|
||||
# Obtain access token, initialize API
|
||||
try:
|
||||
connection_header = get_token(
|
||||
base_url=module.params.get('auth_keycloak_url'),
|
||||
validate_certs=module.params.get('validate_certs'),
|
||||
auth_realm=module.params.get('auth_realm'),
|
||||
client_id=module.params.get('auth_client_id'),
|
||||
auth_username=module.params.get('auth_username'),
|
||||
auth_password=module.params.get('auth_password'),
|
||||
client_secret=module.params.get('auth_client_secret'),
|
||||
)
|
||||
connection_header = get_token(module.params)
|
||||
except KeycloakError as e:
|
||||
module.fail_json(msg=str(e))
|
||||
kc = KeycloakAPI(module, connection_header)
|
||||
|
|
|
@ -81,7 +81,7 @@ author:
|
|||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Create a Keycloak group
|
||||
- name: Create a Keycloak group, authentication with credentials
|
||||
community.general.keycloak_group:
|
||||
name: my-new-kc-group
|
||||
realm: MyCustomRealm
|
||||
|
@ -93,6 +93,16 @@ EXAMPLES = '''
|
|||
auth_password: PASSWORD
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Create a Keycloak group, authentication with token
|
||||
community.general.keycloak_group:
|
||||
name: my-new-kc-group
|
||||
realm: MyCustomRealm
|
||||
state: present
|
||||
auth_client_id: admin-cli
|
||||
auth_keycloak_url: https://auth.example.com/auth
|
||||
token: TOKEN
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Delete a keycloak group
|
||||
community.general.keycloak_group:
|
||||
id: '9d59aa76-2755-48c6-b1af-beb70a82c3cd'
|
||||
|
@ -217,30 +227,25 @@ def main():
|
|||
realm=dict(default='master'),
|
||||
id=dict(type='str'),
|
||||
name=dict(type='str'),
|
||||
attributes=dict(type='dict')
|
||||
attributes=dict(type='dict'),
|
||||
)
|
||||
|
||||
argument_spec.update(meta_args)
|
||||
|
||||
module = AnsibleModule(argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
required_one_of=([['id', 'name']]))
|
||||
required_one_of=([['id', 'name'],
|
||||
['token', 'auth_realm', 'auth_username', 'auth_password']]),
|
||||
required_together=([['auth_realm', 'auth_username', 'auth_password']]))
|
||||
|
||||
result = dict(changed=False, msg='', diff={}, group='')
|
||||
|
||||
# Obtain access token, initialize API
|
||||
try:
|
||||
connection_header = get_token(
|
||||
base_url=module.params.get('auth_keycloak_url'),
|
||||
validate_certs=module.params.get('validate_certs'),
|
||||
auth_realm=module.params.get('auth_realm'),
|
||||
client_id=module.params.get('auth_client_id'),
|
||||
auth_username=module.params.get('auth_username'),
|
||||
auth_password=module.params.get('auth_password'),
|
||||
client_secret=module.params.get('auth_client_secret'),
|
||||
)
|
||||
connection_header = get_token(module.params)
|
||||
except KeycloakError as e:
|
||||
module.fail_json(msg=str(e))
|
||||
|
||||
kc = KeycloakAPI(module, connection_header)
|
||||
|
||||
realm = module.params.get('realm')
|
||||
|
|
|
@ -11,6 +11,16 @@ from ansible_collections.community.general.plugins.module_utils.identity.keycloa
|
|||
from ansible.module_utils.six import StringIO
|
||||
from ansible.module_utils.six.moves.urllib.error import HTTPError
|
||||
|
||||
module_params_creds = {
|
||||
'auth_keycloak_url': 'http://keycloak.url/auth',
|
||||
'validate_certs': True,
|
||||
'auth_realm': 'master',
|
||||
'client_id': 'admin-cli',
|
||||
'auth_username': 'admin',
|
||||
'auth_password': 'admin',
|
||||
'client_secret': None,
|
||||
}
|
||||
|
||||
|
||||
def build_mocked_request(get_id_user_count, response_dict):
|
||||
def _mocked_requests(*args, **kwargs):
|
||||
|
@ -58,16 +68,22 @@ def mock_good_connection(mocker):
|
|||
)
|
||||
|
||||
|
||||
def test_connect_to_keycloak(mock_good_connection):
|
||||
keycloak_header = get_token(
|
||||
base_url='http://keycloak.url/auth',
|
||||
validate_certs=True,
|
||||
auth_realm='master',
|
||||
client_id='admin-cli',
|
||||
auth_username='admin',
|
||||
auth_password='admin',
|
||||
client_secret=None
|
||||
)
|
||||
def test_connect_to_keycloak_with_creds(mock_good_connection):
|
||||
keycloak_header = get_token(module_params_creds)
|
||||
assert keycloak_header == {
|
||||
'Authorization': 'Bearer alongtoken',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
|
||||
def test_connect_to_keycloak_with_token(mock_good_connection):
|
||||
module_params_token = {
|
||||
'auth_keycloak_url': 'http://keycloak.url/auth',
|
||||
'validate_certs': True,
|
||||
'client_id': 'admin-cli',
|
||||
'token': "alongtoken"
|
||||
}
|
||||
keycloak_header = get_token(module_params_token)
|
||||
assert keycloak_header == {
|
||||
'Authorization': 'Bearer alongtoken',
|
||||
'Content-Type': 'application/json'
|
||||
|
@ -87,15 +103,7 @@ def mock_bad_json_returned(mocker):
|
|||
|
||||
def test_bad_json_returned(mock_bad_json_returned):
|
||||
with pytest.raises(KeycloakError) as raised_error:
|
||||
get_token(
|
||||
base_url='http://keycloak.url/auth',
|
||||
validate_certs=True,
|
||||
auth_realm='master',
|
||||
client_id='admin-cli',
|
||||
auth_username='admin',
|
||||
auth_password='admin',
|
||||
client_secret=None
|
||||
)
|
||||
get_token(module_params_creds)
|
||||
# cannot check all the message, different errors message for the value
|
||||
# error in python 2.6, 2.7 and 3.*.
|
||||
assert (
|
||||
|
@ -125,15 +133,7 @@ def mock_401_returned(mocker):
|
|||
|
||||
def test_error_returned(mock_401_returned):
|
||||
with pytest.raises(KeycloakError) as raised_error:
|
||||
get_token(
|
||||
base_url='http://keycloak.url/auth',
|
||||
validate_certs=True,
|
||||
auth_realm='master',
|
||||
client_id='admin-cli',
|
||||
auth_username='notadminuser',
|
||||
auth_password='notadminpassword',
|
||||
client_secret=None
|
||||
)
|
||||
get_token(module_params_creds)
|
||||
assert str(raised_error.value) == (
|
||||
'Could not obtain access token from http://keycloak.url'
|
||||
'/auth/realms/master/protocol/openid-connect/token: '
|
||||
|
@ -154,15 +154,7 @@ def mock_json_without_token_returned(mocker):
|
|||
|
||||
def test_json_without_token_returned(mock_json_without_token_returned):
|
||||
with pytest.raises(KeycloakError) as raised_error:
|
||||
get_token(
|
||||
base_url='http://keycloak.url/auth',
|
||||
validate_certs=True,
|
||||
auth_realm='master',
|
||||
client_id='admin-cli',
|
||||
auth_username='admin',
|
||||
auth_password='admin',
|
||||
client_secret=None
|
||||
)
|
||||
get_token(module_params_creds)
|
||||
assert str(raised_error.value) == (
|
||||
'Could not obtain access token from http://keycloak.url'
|
||||
'/auth/realms/master/protocol/openid-connect/token'
|
||||
|
|
Loading…
Reference in New Issue