-
Notifications
You must be signed in to change notification settings - Fork 1.7k
New Module: Keycloak User #3996
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
This comment has been minimized.
This comment has been minimized.
8000
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
@fynncfchen @pmdumuid what's the current state of this PR? @fynncfchen are you planning to do more changes? @pmdumuid do you think there should be more changes? |
I'm good, thanks, any more comments @pmdumuid? |
@felixfontein - I presume there should be some commit fragment files created with this PR? There's a lot of reformatting that occurred within this PR (within the file, I'll take another look at this PR tonight, (comments coming shortly) |
data=json.dumps(userrep), validate_certs=self.validate_certs) | ||
except Exception as e: | ||
self.module.fail_json(msg="Could not create user %s in realm %s: %s" | ||
% (userrep['user_username'], realm, str(e))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is the key, user_username
being used in the error message?
There seems to be no test to ensure that when creating a user, the username has been specified.
The API docs also indicate that the username field is actually username
and not user_username
- see https://www.keycloak.org/docs-api/12.0/rest-api/#_userrepresentation.
data=json.dumps(userrep), validate_certs=self.validate_certs) | ||
except Exception as e: | ||
self.module.fail_json(msg='Could not update user %s in realm %s: %s' | ||
% (userrep['username'], realm, str(e))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This error message expects username
to be present, but your document states:
This parameter is required only when creating or updating the user
Can I suggest this be swapped with:
userrep.get('username', "<User-CID:%s>" % userrep.get('id', None))
so that an error isn't thrown whilst trying to formulate a nice error message.
if id is None and username is None: | ||
# prefer an exception since this is almost certainly a programming error in the module itself. | ||
raise Exception( | ||
"Unable to delete user - one of user ID or username must be provided.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test for is or username being present should also exist for update_user
.
We should also throw an error if both are provided.
|
||
module = AnsibleModule(argument_spec=argument_spec, | ||
supports_check_mode=True, | ||
required_one_of=([['id', 'username'], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be beneficial to use mutually_exclusive
i.e. to allow username
, or id
but not both? - See https://docs.ansible.com/ansible/latest/dev_guide/developing_program_flow_modules.html#argument-spec
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with you, for sure. The only reason why I choose to have similar solutions from other files instead of best solution, is that I think keeping consistency for those files is more important in the first place, then we can think about to refactoring later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding such restrictions to existing modules is a breaking change and needs a long deperecation period (and extra code to inform users about it), that's usually the main reason for it not being done. But for new modules we should better stick to the best solution :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a brand new module (keycloak_user.py
didn't exist prior to this PR), so I didn't think the concern of deprecation applied, (i.e we should instead try to get it as right as possible up front)
argument_spec = keycloak_argument_spec() | ||
|
||
meta_args = dict( | ||
# Remove alias `username` on `auth_username` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could I propose, (not for this PR) we remove the alias, username
and just require all to use auth_username
to avoid any ambiguity?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good suggestion, but I think that's a big deal, we can have a dedicated PR for this, how about that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree, (hence why I said, "not for this PR").
|
||
:param username: The username of the user. A lookup will be performed to retrieve the user ID. | ||
:param realm: Return the groups of this realm (default "master"). | ||
""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does this need to return a list (and the received thus have a lot of logic around the response, and so forth)?
Surely, keycloak only allows one user with a username per realm, so the response could be None or the user object rather than a list of objects.
The calling code could then be reduced to:
found_user = kc.get_user_by_username(username, realm=realm)
before_user = {} if not found_user else found_user
after_user = kc.get_users_by_username(username, realm=realm)[0] | ||
|
||
result['end_state'] = after_user | ||
result['user'] = result['end_state'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a benefit to populating both 'user'
and 'end_state'
? Can I suggest we just go with 'end_state'
? (as per all the other key cloak modules.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AFAIK the 'user'
object is to keep consistency with other module:
- Deprecated return value, it will be removed in community.general 6.0.0. Please use the return value I(end_state) instead.
but some of the module doesn't have such object, so I have no idea for which I should follow or not.
Maybe we can keep consistency with this and remove it on version 6.0.0
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would rather not introduce it in the first place. It is only left in the other modules because it will be removed eventually, there's no need to add more cruft that needs to be removed :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, this this is a new module (keycloak_user.py
didn't exist prior to this PR) so I don't think we should be adding it.
result['changed'] = True | ||
|
||
if module._diff: | ||
result['diff'] = dict(before=before_user, after='') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given that the before_user
is a dict, I think it's best if the after
is also a dict rather than an empty string.
if found_users is None or found_users == []: | ||
before_user = {} | ||
else: | ||
before_user = found_users[0] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The documents above indicate say that username
is only required when doing a create, but I can't (from the code) see how the value of before_user
(which is used below when deleting a user) is populated properly when the username is not supplied.
I think it would be beneficial to add some integration tests (for example as described in |
You mean changelog fragment - these are not required for new modules, so none is needed here.
I would really avoid reformatting. |
users_url = URL_USERS.format(url=self.baseurl, realm=realm) | ||
try: | ||
return open_url(users_url, method='POST', headers=self.restheaders, | ||
data=json.dumps(userrep), validate_certs=self.validate_certs) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This (and others) need to be adjusted similar to #4178.
# Process an update | ||
|
||
# no changes | ||
if desired_user == before_user: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@fynncfchen First, thank you for the module.I have been testing it against both keycloak 14 and 16.1 during the day and I think I found a bug.Not sure how to fix it sadly.
Consider the following snippet:
- keycloak_user:
auth_client_id: admin-cli
auth_keycloak_url: ...
token: ...
username: test
realm: master
credentials:
- type: password
temporary: false
value: test
state: present
During the first execution the user test will be correctly created (“User test has been created with ID blabla”).
On subsequent runs instead of doing nothing the user is updated every time.
In these cases the variables before_user
and after_user
are strictly identical.
The difference comes with desired_users
which contains a key credentials
while before_user
and after_user
don't.
Obviously keycloak is not going to return clear text passwords to compare against and simply comparing before_user
and after_user
is not going to catch a password change.
needs_info |
This New Module PR contains a symbolic link from plugins/modules/ to the actual Python file. Since #4562 this is no longer necessary and will soon be flagged as an error. Instead you need to add an entry to meta/runtime.yml, similar to this one: https://github.com/ansible-collections/community.general/blob/main/meta/runtime.yml#L21-L22 See also our updated instructions on creating new modules: https://github.com/ansible-collections/community.general/blob/main/CONTRIBUTING.md#creating-new-modules-or-plugins |
@fynncfchen This pullrequest is waiting for your response. Please respond or the pullrequest will be closed. |
@fynncfchen You have not responded to information requests in this pullrequest so we will assume it no longer affects you. If you are still interested in this, please create a new pullrequest with the requested information. |
SUMMARY
Add
keycloak_user
to provide user management via Keycloak Admin API.ISSUE TYPE
COMPONENT NAME
keycloak_user
ADDITIONAL INFORMATION
Usage: