[keycloak-dev] Improving support for LDAP backed Keycloak
mposolda at redhat.com
Wed Feb 20 08:03:46 EST 2019
On 18/02/2019 18:39, Dan Hardiker wrote:
> Hi All,
> Sorry for such a long first post. Here we go!
> I want to look at https://issues.jboss.org/browse/KEYCLOAK-5571 as it is impacting us. I’m happy to contribute code or write a blog on what configuration settings are needed to achieve this. While the SAGA has more context, here’s a few of my currently burning questions:
> 1. What implements the org.keycloak.admin.client.resource.UserResource.update(UserRepresentation) and UserRepresentation ...toRepresentation() interface method? (from the integration/admin-client directory - I can’t find the business logic)
The implementation is "auto-generated" proxy class, which just invokes
the particular admin REST endpoint - for example
org.keycloak.admin.client.resource.UserResource.update is invoked the
server-side REST endpoint
> 2. What would be the right approach to wire up the admin ui User Enabled toggle to a LDAP boolean field, and where in the codebase would that go? (if you can cite examples of similar that would be great)
UserAttributeLDAPStorageMapper is the class where you can look at. As you can see in the "proxy" method, the proxied model implements setFirstName, setLastNAme, setEmail .
It seems that few more things need to be added (EG. setEnabled, setEmailVerified)
The automated test may need to be added somewhere in LDAPProvidersIntegrationTest IMO.
> 3. What is the best way to go about setting up an IDE for development? Just importing the root POM into IDEA doesn’t seem to cut it.
Importing the root POM into IDEA works fine for me.
> 4. If I provide a patch for this, is this something that might be considered for pulling into master?
Yes. There needs to be few simple rules met when you want something to
be considered into master. We're working on improving guidelines for
contributors. See details in this PR:
https://github.com/keycloak/keycloak/pull/5886/files . You just already
started the discussion on keycloak-dev mailing list, which is nice 1st
step. Good luck with moving forward on your contribution!
> I am interested in all of the features within KEYCLOAK-5571, as a few other requirements, but I’m happy to start here and treat the others as atomic suggested changes. They may include:
> * Supporting incremented default values for new users (the uidNumber must be unique and it should be 1 greater than the highest uidNumber that the system can see … i.e. the next available UID).
> * Supporting out-of-band password recovery (where by a code is sent via a trusted path [text message, telephone call, in person conversation with the user] which can be used to reset their password - ideally in combination with another stored value, such as their employee id / tax id / post code / something else which is relatively static but relatively unknown) - this could be developed outside of Keycloak of course, but would ideally be within the same system.
> If addressing KEYCLOAK-5571 goes well, I would be interested in continuing to contribute down these paths.
> Thanks for your time, I would love to get involved … I just need a bit of help.
> THE SAGA:
> Apologies if this message should be in keycloak-users, and if any of it seems incoherent. I’ve been fighting in circles all weekend and I have to admit that I’m not entirely sure I’m approaching the problem correctly. Please bear with me as I’m not entirely sure how to articulate things at this point, but I know I need help!
> Problem statement: we are currently using OpenLDAP to manage access to our systems. However, the administration interface is crude and it lacks SAML/OIDC support for integrating systems like Google Suite, AWS Console, Office 365 and others. It also lacks a self service console where users can mange their own accounts. Keycloak at first glance looks ideal - especially as it allows us to continue using OpenLDAP as the primary source of truth, with Keycloak used to enhance the user experience giving self service and integration with SAML/OIDC clients.
> As per the docs, some mapping is required to have OpenLDAP support the storage of Keycloak data within the OpenLDAP schemas. Unfortunately, I’ve not bee able to find documentation for what those fields names in Keycloak can be and how I should alter my OpenLDAP schema to support them. I found KEYCLOAK-5571 which appears to cover at least some of the issues I’m having. Amongst other things, I’m a Java developer, so I’m comfortable with working in code and submitting patches. Assuming that the answer isn’t configuration, is this something that would be valuable to contribute? If so, is there any advice that this list can offer on where to start?
> What follows is my journey as an outsider into trying to figure out things myself. This may or may not be of interest - but given this list gets indexed by Google, it might help someone in future. Seeing that issue (KEYCLOAK-5571) I figured the best place to start would be the admin ui where you enable/disable users. I thought that I would start at the browser and try to figure out what the enabled/disable user toggle did when saved, trace that into the server side endpoint that picked up that representation and hopefully find out where & why it didn’t make it through to LDAP.
> I noticed that there was a PUT to http://localhost/auth/admin/realms/master/users/f:$UUID:$USERNAME and as part of the JSON payload was “enabled: false”. At this point I started grepping around in the Keycloak code. I figured that org.keycloak.admin.client.resource.UserResource.update(UserRepresentation) Interface was what was being called, but unfortunately when I opened up the root POM then IDEA only saw the files as plain text and none of the Intelisense worked, I could grep around the code though. When I opened up the integration/admin-client/pom.xml it recognise the Java files, however I wasn’t able to find what was implementing this. If found "public static UserRepresentation toRepresentation(KeycloakSession session, RealmModel realm, UserModel user)” in server-spi-private org.keycloak.models.util.ModelToRepresentation, but couldn’t find the glue which connects them together. I’m guessing there might be some WildFly or other magic going on which I’m not aware of?
> Seeing the “enabled: false” lead me to think that I might be able to create a user-attribute-ldap-mapper from the user model attribute “enabled” to an “enabled” LDAP attribute I added to our schema to test. The LDAP attribute has SYNTAX 220.127.116.11.4.1.1418.104.22.168.7 (a binary attribute) and I’ve checked I can set that to TRUE / FALSE appropriately. I set it to be mandatory in LDAP and set it to be a Binary Attribute - however when I save it says "Error! With Binary attribute enabled, the 'Always read value from LDAP' must be enabled too” - however there is no “Always read value from LDAP” option! However, after enabling Import Users in the LDAP user federation settings, “Always read value from LDAP” becomes available. It’s not clear if Binary Attributes are supported only in this configuration, but ideally I would like to not Import Users as I prefer LDAP to be the authoritative source. After this I can disabled Import Users and the configuration still seemingly remains valid without any errors in the logs. That said, it’s not erroring about not properly persisting the enabled state to LDAP...
> If I go across to the user in the admin ui, even though enabled is set to FALSE in LDAP, the toggle is showing as enabled in the UI. The JSON it gets for the UserRepresentation on the client side is “enabled”:true which explains the state of the toggle. If I stick Wireshark locally, setup a Docker with OpenLDAP and configure it appropriately, sniffing traffic I can see that the enabled attribute for my user comes back as FALSE. So there is something going wrong when trying to build that UserRepresentation. I suspect at the root of the KEYCLOAK-5571 issue. If I change the toggle to false in the UI and save, then reload the page, the toggle is back to true - when it persists to the LDAP server, it’s sending enabled: FALSE - this doesn’t make sense, but it might be just repeating back to LDAP what it read in without changing that field. If I change the name as well, it does send those fields updated, but enabled remains FALSE in the LDAP server.
> Given that I didnt get very far with the UserRepresentation angle, I thought about going down the FederatedStorage - something must map the model into LDAP, as changes to the first name / last name, and the other attributes seem to be persisted and loaded in LDAP just fine. In my grepping around in server-spi I found a org.keycloak.models.UserModel Interface, which had a org.keycloak.storage.adapter.AbstractUserAdapterFederatedStorage implementation with a ENABLED_ATTRIBUTE = “ENABLED” field and isEnabled / setEnabled methods which getFirstAttribute(ENABLED_ATTRIBUTE) / setSingleAttribute(ENABLED_ATTRIBUTE, Boolean.toString(enabled). The class comment has:
> * Assumes everything is managed by federated storage except for username. getId() returns a default value
> * of "f:" + providerId + ":" + getUsername(). UserModel properties like enabled, firstName, lastName, email, etc. are all
> * stored as attributes in federated storage.
> I’m not sure how the case difference between “enabled” in the UserModel properties and “ENABLED” as listed in the class field is connected - but there must be a mapping somewhere, as “firstName” is similarly “FIRST_NAME” and that maps just fine. I found model/jpa contained org.keycloak.models.jpa.entities/UserEntity which had @Column(name = "ENABLED”) protected boolean enabled, perhaps this is the link and even with Import Users disabled it always goes through the database?
> I’ve yet to find the trigger which calls the mapper to run which persists into the database. Part of the problem is that I’m acutely aware my IDE is not setup to effectively jump around the code base, or to effectively attach my IDE as a debugger so I can add breakpoints and step through the code to figure out what happens where. I’ve just turned on trace logging - but this is giving me a wall of text which may take sometime to process. I’ve also yet to comb through the H2 DB to see if there’s cause there.
> Any assistance on this would be most welcome.
> ON ANOTHER NOTE:
> I checked out the code and ran the build as documented against Java 8 on my mac, but unfortunately it failed. I ignored it and progressed, but here’s some excerpts from the output:
> [INFO] Keycloak Integration TestSuite - deprecated ........ FAILURE [07:04 min]
> [ERROR] Errors:
> [ERROR] OIDCKeyCloakServerBrokerBasicTest.testLogoutWorksWithTokenTimeout:131 » Processing
> [ERROR] OIDCKeycloakServerBrokerWithConsentTest.before:84 » Processing java.lang.NoSuc...
> [ERROR] BrokenUserStorageTest.testBootWithBadProviderId:118 » Processing java.lang.NoS...
> [ERROR] JaxrsBasicAuthTest.testBasic:120 » NoSuchMethod org.apache.commons.io.output.D...
> [ERROR] JaxrsFilterTest.testBasic:129 » NoSuchMethod org.apache.commons.io.output.Defe...
> [ERROR] Tests run: 238, Failures: 0, Errors: 5, Skipped: 32
> I guess this shouldn’t happen on a fresh check out & a following of the instruction.
> If you made it this far - bravo!
> Thanks again,
> Dan Hardiker | Adaptavist
> keycloak-dev mailing list
> keycloak-dev at lists.jboss.org
More information about the keycloak-dev