12 Replies Latest reply on Sep 2, 2014 10:05 AM by pcraveiro

    JPA + LDAP auth

    sslamm

      I have the following case: some users are already stored in the LDAP, without any roles\groups set. My application should use these users and assign groups and roles for them, which should be stored in the database. And also the application should provide an ability to add and manage new users with groups and roles that have to be stored in the database. Groups and roles for both user types should be the same.

       

      I have read the documentation and look through the tutorials but have not found anything that can describes my issue.

       

      Could you possible advice me how this situation can be organized via picketlink?

        • 1. Re: JPA + LDAP auth
          pcraveiro

          Hey Valentin,

           

              I would suggest you to take a quick look at this documentation:

           

                  http://docs.jboss.org/picketlink/2/latest/reference/html-single/#Providing_Multiple_Stores_for_a_Configuration

           

              Basically, what you need is provide the configuration for both stores, defining which types must be supported by each one:

           

          IdentityConfigurationBuilder builder = new IdentityConfigurationBuilder();
          
          builder
               .named("multiple.store.config")
                    .stores()
                         .jpa()
                              .supportGlobalRelationship(Relationship.class) // tells that all Relationship types should be managed by the JPA store
                              .supportAttributes(true) // tells that ad-hoc attributes should be supported by the JPA store
                         .ldap()
                              .mapping(User.class)
                              .mapping(Group.class)
                              .mapping(Role.class)
                              .supportCredentials(true) // only the LDAP store should be used to authenticate users
          

              

               If you want some code, please take a look at the following test case:

           

                  https://github.com/picketlink/picketlink/blob/master/modules/idm/tests/src/test/java/org/picketlink/test/idm/testers/SingleConfigLDAPJPAStoreConfigurationTester.java

           

               The configuration above is using JPA for relationships and attributes only. And LDAP to store users, groups and roles.

           

          Regards.

          Pedro Igor

          1 of 1 people found this helpful
          • 2. Re: JPA + LDAP auth
            sslamm

            Thanks Pedro.

            But this is not exactly what I am looking for. Since I need to store users in both JPA and LDAP, I suppose I need to set supportCredentials to true for both stores, but it is not possible: IdentityConfiguration validation fails in this case. Also it is not possible to map the same entity, User for example, to both stores. Do you know how this issues can be solved?

            • 3. Re: Re: JPA + LDAP auth
              pcraveiro

              Hi Valentin,    

               

                  Yes, this is also possible.  To understand how, let me give you a brief overview about the PicketLink IDM Configuration.

               

                  You can build a PartitionManager using a single or multiple IdentityConfiguration instances. Those instances are usually built using the Configuration API, so if you want to provide a single configuration you just need to:

               

              IdentityConfigurationBuilder builder = new IdentityConfigurationBuilder();
              
              builder
                   .named("my.single.config")
                        .stores()
                             .jpa() // jpa configuration
                             .ldap()// ldap configuration
              
              DefaultPartitionManager partitionManager = new DefaultPartitionManager(builder.buildAll());
              

               

                  That was exactly what I told you before. When using a single configuration, a identity type can only be supported by a single identity store. That is why you're getting that error. The same thing with credential or even attribute support, only one store can provide those features.

               

                  However, with multiple configurations the story is a bit different and it provides you great flexibility. To provide multiple configurations you just need to:

               

              IdentityConfigurationBuilder builder = new IdentityConfigurationBuilder();
              
              
              builder
                   .named("my.jpa.config")
                        .stores()
                             .jpa()
                                  .supportAllFeatures()
              
                   .named("my.ldap.config")
                        .stores()
                             .ldap()
                                  .supportType(User.class, Agent.class)
                                  .supportCredentials(true)
              
              

               

                   As you can see, we're providing two "named" configurations. One with the JPA and another for the LDAP store. Please note that the configuration above is supporting all features (including all types) in JPA. But restricting which features/types are supported by LDAP. You can take a look at this test case for a working example configuration:

               

                      https://github.com/picketlink/picketlink/blob/master/modules/idm/tests/src/test/java/org/picketlink/test/idm/testers/MultipleIdentityConfigurationTester.java

               

                  If you're using CDI, you can provide multiple configurations as demonstrated by the test case below. Basically, you just need a producer method that returns a IdentityConfiguration instance. You can provide multiple producer methods.

               

                        https://github.com/picketlink/picketlink/blob/master/tests/src/test/java/org/picketlink/test/idm/config/ProduceMultipleIdentityConfigurationTestCase.java#L89

               

                  Another important aspect when using multiple configurations is that you need to properly manage partitions, so you can define whether your application should use the JPA or LDAP config to manage your users, etc.

               

                      picketlink/modules/idm/tests/src/test/java/org/picketlink/test/idm/config/MultipleIdentityConfigurationTestCase.java at …

               

                  Fell free to join us on #picketlink/freenode, if you need more guidance.

               

              Regards.

              • 4. Re: Re: JPA + LDAP auth
                shane.bryzak

                Just to add to this advice given by Pedro, another possibility is that you subclass your user class and store one type in LDAP, one in the database.  So for example say your User class looks like this:

                 

                public class User implements Account {
                  // properties, getters and setters
                }
                

                 

                Then extend this to create another user class:

                 

                public class LDAPUser extends User { }
                

                 

                Once you've done that, configure PicketLink to store User identities in the database, and LDAPUser identities in the LDAP directory.  The only additional step should be adjusting your authentication logic so that it will attempt authentication using both identity types (i.e. try the first one, if it fails then try the second).

                • 5. Re: JPA + LDAP auth
                  sslamm

                  Thanks, Pedro. If I configure jpa and ldap for separate partitions, will they be able to use the same groups and roles that are store in jpa?

                   

                  Thanks, Shane. Is it possible to inject\acquire StoreSelector instance in the custom CredentialHandler? Seems that I need to switch between JPAIdentityStore and LDAPIdentityStore in my CredentialHandler realization and as far as I can see, they can be acquired via StoreSelector.getStoreForIdentityOperation(...)

                  • 6. Re: JPA + LDAP auth
                    shane.bryzak

                    I think that you would need to implement the switching logic at a higher level than the CredentialHandler.  Perhaps it might be necessary to also have two Credentials implementations to support both types of authentication.

                    1 of 1 people found this helpful
                    • 7. Re: JPA + LDAP auth
                      pcraveiro

                      Yes, you are still able to use the same groups and roles for users stored in ldap and jpa. The test case below is demonstrating how you can grant a role for different users stored in different partitions/stores.

                       

                           https://github.com/picketlink/picketlink/blob/master/modules/idm/tests/src/test/java/org/picketlink/test/idm/config/MultipleIdentityConfigurationTestCase.java#L105

                       

                      Regards.   

                      • 8. Re: JPA + LDAP auth
                        sslamm

                        When I use separate partitions I noticed one strange thing:

                        * when acquiring JPA user via BasicModel.getUser(), users partition property (AbstractIdentityType.partiton) is set to 'default'

                        * when acquiring LDAP user via BasicModel.getUser(), users partition property is also 'default'

                        I have found that is property is always set to 'default' in the DefaultIdentityQuery.getResultList() method.

                        I suppose that LDAP and JPA users should have different 'partition' property...

                         

                        Could you possibly clarify, it is normal or I have some configuration issues?

                        • 9. Re: JPA + LDAP auth
                          alex1985

                          Hi,

                           

                          anything new here? I have the same problem. I have an ldap-directory which conatains user, These users should get an role within my application. The roles and the releationships should be stored in an file base store. Therefore i have two configs (see below). ldap should only be in read-mode and be used to authenticate users which works fine for me.

                          The problem is that i want to add all users of ldap-store the role for e.g. 'member'. if i call BasicModel.grantRole() and after that BasicModel.hasRole() i got an error at DefaultRelationshipQuery.resolveIdentityTypes(). -->

                           

                          1. org.picketlink.idm.IdentityManagementException: PLIDM000500: Could not query Relationship using query [org.picketlink.idm.query.internal.DefaultRelationshipQuery@5189112b].
                          2.   at org.picketlink.idm.query.internal.DefaultRelationshipQuery.getResultList(DefaultRelationshipQuery.java:142)
                          3.   at org.picketlink.idm.model.basic.BasicModel.hasRole(BasicModel.java:447)
                          4.   at de.hr.filesearch.controller.security.LDAPIdentityManagmentConfigurationTest.testRoleManagement(LDAPIdentityManagmentConfigurationTest.java:150)
                          5.   at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
                          6.   at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
                          7.   at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
                          8.   at java.lang.reflect.Method.invoke(Method.java:606)
                          9.   at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
                          10.   at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
                          11.   at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
                          12.   at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
                          13.   at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
                          14.   at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
                          15.   at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
                          16.   at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
                          17.   at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
                          18.   at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
                          19.   at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
                          20.   at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
                          21.   at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
                          22.   at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
                          23.   at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
                          24.   at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
                          25.   at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
                          26.   at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
                          27.   at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
                          28.   at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
                          29.   at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
                          30. Caused by: org.picketlink.idm.IdentityManagementException: Referenced IdentityType [4f1e99a2-6ef9-4d90-bd0c-d3e5505f8726] from relationship [class org.picketlink.idm.model.basic.Grant] does not exists in any store.
                          31.   at org.picketlink.idm.query.internal.DefaultRelationshipQuery.resolveIdentityTypes(DefaultRelationshipQuery.java:176)
                          32.   at org.picketlink.idm.query.internal.DefaultRelationshipQuery.getResultList(DefaultRelationshipQuery.java:130)
                          33.   ... 27 more

                           

                           

                          Thanks for help

                           

                           

                          1. @PicketLink
                          2.   @Produces
                          3.   public PartitionManager producePartitionManager()
                          4.   {
                          5.   if ( this.partitionManager == null )
                          6.   {
                          7.   IdentityConfigurationBuilder builder = new IdentityConfigurationBuilder();
                          8.   builder
                          9.   .named( FILE_CONFIGURATION_NAME )
                          10.   .stores()
                          11.   .file()
                          12.   .supportAllFeatures()
                          13.   .named( LDAP_CONFIGURATION_NAME )
                          14.   .stores()
                          15.   .ldap()
                          16.   .baseDN( BASE_DN )
                          17.   .bindDN( bindDn )
                          18.   .bindCredential( bindCredential )
                          19.   .url( LDAP_URL )
                          20.   .supportType( Agent.class, User.class )
                          21.   .supportCredentials( true )
                          22.   .mapping( Agent.class )
                          23.     .baseDN( AGENT_DN_SUFFIX )
                          24.     .objectClasses( "inetOrgPerson", "organizationalPerson" )
                          25.     .attribute( "loginName", UID, true )
                          26.     .readOnlyAttribute( "createdDate", CREATE_TIMESTAMP )
                          27.   .mapping( User.class )
                          28.   .baseDN( USER_DN_SUFFIX )
                          29.   .objectClasses( "inetOrgPerson", "organizationalPerson" )
                          30.   .attribute( "loginName", UID, true )
                          31.   .attribute( "firstName", CN ).attribute( "lastName", SN )
                          32.   .attribute( "email", EMAIL )
                          33.   .readOnlyAttribute( "createdDate", CREATE_TIMESTAMP );
                          34.   DefaultPartitionManager defaultPartitionManager = new DefaultPartitionManager( builder.buildAll() );
                          35.   if ( defaultPartitionManager.getPartition( Realm.class, Realm.DEFAULT_REALM ) == null )
                          36.   {
                          37.   defaultPartitionManager.add( new Realm( Realm.DEFAULT_REALM ), FILE_CONFIGURATION_NAME );
                          38.   }
                          39.   if ( defaultPartitionManager.getPartition( Realm.class, LDAP_PARTITION ) == null )
                          40.   {
                          41.   defaultPartitionManager.add( new Realm( LDAP_PARTITION ), LDAP_CONFIGURATION_NAME );
                          42.   }
                          43.   this.partitionManager = defaultPartitionManager;
                          44.   }
                          45.   return this.partitionManager;
                          46.   }
                          • 10. Re: JPA + LDAP auth
                            pcraveiro

                            Hey Alexander,

                             

                                 Can you try using the JPA store instead of the File store ? I'm not 100% sure if the file store is able to resolve types stored in a different identity store.

                            • 11. Re: JPA + LDAP auth
                              alex1985

                              Hi,

                               

                              i spent some time in debugging and it seems that the problem for the users which already exists in ldap-store is inside org.picketlink.idm.query.internal.DefaultIdentityQuery.getResultList(). The partition of the user which was found inside this methode is null and that is why org.picketlink.idm.util.IDMUtil.configureDefaultPartition(IdentityType, IdentityStore, PartitionManager) will set it to the default partition.

                               

                              Is there anything which i can change? It was found via a query of an identityManager which uses my ldap realm/partition so why this partition is not set as partition of the user which was found inside org.picketlink.idm.query.internal.DefaultIdentityQuery.getResultList()?

                               

                               

                              A workaournd for me is now to manualy set the partion to the ldap-partition if i found the user, but i don't think that corresponds to the expected behaviour.

                              • 12. Re: JPA + LDAP auth
                                pcraveiro

                                Hi,

                                 

                                    The LDAP identity store does not support partition management. That is why it is always set to the Realm.DEFAULT_REALM.

                                 

                                    I was reviewing your configuration and I think you should specify which types are supported from your file/jpa config instead of using supportAllFeatures(). This can cause some confusion to PicketLink, because you are telling that both file/jpa and ldap config are supporting User, what I think is not your objective.

                                 

                                    I would define the config like this:

                                 

                                 IdentityConfigurationBuilder builder = new IdentityConfigurationBuilder();
                                       
                                 builder
                                     .named("default.config")
                                         .stores()
                                             .file()
                                                 .supportType(Role.class, Group.class, Relationship.class) // we explicity define the types here
                                                 .supportAttributes(true)
                                                 .supportCredentials(false) // credentials are only support by the ldap store config
                                             .ldap()
                                                 .baseDN(BASE_DN)
                                                 .bindDN(bindDn)
                                                 .bindCredential(bindCredential)
                                                 .url(LDAP_URL)
                                                 .supportCredentials(true)
                                                 .mapping(Agent.class)
                                                     .baseDN(AGENT_DN_SUFFIX)
                                                     .objectClasses("inetOrgPerson", "organizationalPerson")
                                                     .attribute("loginName", UID, true)
                                                     .readOnlyAttribute("createdDate", LDAPConstants.CREATE_TIMESTAMP)
                                                 .mapping(User.class)
                                                     .baseDN(USER_DN_SUFFIX)
                                                     .objectClasses("inetOrgPerson", "organizationalPerson")
                                                     .attribute("loginName", UID, true)
                                                     .attribute("firstName", LDAPConstants.CN).attribute( "lastName", SN )
                                                     .attribute("email", LDAPConstants.EMAIL)
                                                     .readOnlyAttribute("createdDate", LDAPConstants.CREATE_TIMESTAMP);
                                

                                 

                                I think you can also use a single config with both stores in it.

                                 

                                Also, please notice that you don't need to call supportType in LDAP config. You just need to use mapping.