├── readme.md
├── step-01
├── apache-ds-tutorial.ldif
├── images
│ ├── ldap-00-create-server.png
│ ├── ldap-01-configure-server.png
│ ├── ldap-02-create-partition.png
│ ├── ldap-03-start-server.png
│ ├── ldap-04-create-connection.png
│ ├── ldap-05-import-ldiff.png
│ ├── ldap-06-select-ldiff.png
│ └── ldap-07-browse-structure.png
└── tutorial-01-install-ldap.md
├── step-02
├── images
│ ├── aem-00-setup-logging.png
│ ├── aem-01-find-ldap-idp.png
│ ├── aem-02-configure-ldap-idp.png
│ ├── aem-03-find-synchandler.png
│ ├── aem-04-configure-synchandler.png
│ ├── aem-05-find-loginmodule.png
│ ├── aem-06-configure-loginmodule.png
│ └── aem-07-jaas-console.png
├── ldap-first-config-pkg.zip
└── tutorial-02-configure-aem.md
├── step-03
├── images
│ ├── test-01-useradmin.png
│ ├── test-02-newconfig.png
│ ├── test-03-useradmin-withprops.png
│ ├── test-04-jmx-console.png
│ ├── test-05-jmx-synchandler.png
│ ├── test-06-jmx-syncexternalusers.png
│ └── test-07-useradmin-more.png
├── ldap-config-with-profile-pkg.zip
├── log-snip-01.md
└── tutorial-03-test.md
└── step-04
├── authorizables.json
├── example-idp
├── .gitignore
├── pom.xml
└── src
│ └── main
│ └── java
│ └── com
│ └── adobe
│ └── gems
│ └── exampleidp
│ └── impl
│ ├── ExternalGroupImpl.java
│ ├── ExternalIdentityImpl.java
│ ├── ExternalUserImpl.java
│ └── JsonFileIdentityProvider.java
├── images
├── example-01-find-json-idp.png
├── example-02-configure-idp.png
├── example-03-configure-synchandler.png
├── example-04-configure-loginmodule.png
└── example-05-useradmin.png
├── json-idp-config-pkg.zip
└── tutorial-04-example-idp.md
/readme.md:
--------------------------------------------------------------------------------
1 | AEM 6.1 External Authentication Tutorial
2 | ========================================
3 |
4 | This tutorial goes through the steps to setup and configure a LDAP server and the configure AEM to use example data for authentication.
5 |
6 | As a bonus track, we show how to build and configure your own identity provider.
7 |
8 | Sections
9 | --------
10 |
11 | 1 [Setup Apache Directory Server](step-01/tutorial-01-install-ldap.md)
12 | 2 [Configure LDAP Authentication in AEM](step-02/tutorial-02-configure-aem.md)
13 | 3 [Test LDAP Authentication in AEM](step-03/tutorial-03-test.md)
14 |
15 | Bonus
16 | -----
17 |
18 | 4 [Create your own IDP](step-04/tutorial-04-example-idp.md)
19 |
20 | Resources
21 | ---------
22 | * Example ldif file: [apache-ds-tutorial.ldif](step-01/apache-ds-tutorial.ldif)
23 | * Package with AEM 6.1 LDAP example configurations:
24 | * First config: [ldap-first-config-pkg.zip](step-02/ldap-first-config-pkg.zip)
25 | * Config with profile: [ldap-config-with-profile-pkg.zip](step-03/ldap-config-with-profile-pkg.zip)
26 |
27 | Links
28 | -----
29 | * [Apache Jackrabbit Oak](http://jackrabbit.apache.org/oak)
30 | * [Oak Security Documentation](http://jackrabbit.apache.org/oak/docs/security/overview.html)
31 | * [Apache Directory](http://directory.apache.org/apacheds)
32 | * [Oracle JAAS Authentication Doc](http://docs.oracle.com/javase/8/docs/technotes/guides/security/jgss/tutorials/AcnOnly.html)
33 |
--------------------------------------------------------------------------------
/step-01/apache-ds-tutorial.ldif:
--------------------------------------------------------------------------------
1 | # Sample LDIF data for the ApacheDS v1.0 Basic User's Guide
2 | #
3 | # Some sailors and their ships
4 | # userpassword for all persons is "pass"
5 | #
6 | version: 1
7 |
8 | dn: ou=people,o=sevenSeas
9 | objectclass: organizationalUnit
10 | objectclass: top
11 | description: Contains entries which describe persons (seamen)
12 | ou: people
13 |
14 | dn: ou=groups,o=sevenSeas
15 | objectclass: organizationalUnit
16 | objectclass: top
17 | description: Contains entries which describe groups (crews, for instance)
18 | ou: groups
19 |
20 | dn: ou=crews,ou=groups,o=sevenSeas
21 | objectclass: organizationalUnit
22 | objectclass: top
23 | description: Contains entries which describe ship crews
24 | ou: crews
25 |
26 | dn: ou=ranks,ou=groups,o=sevenSeas
27 | objectclass: organizationalUnit
28 | objectclass: top
29 | description: Contains entries which describe naval ranks (e.g. captain)
30 | ou: ranks
31 |
32 | # HMS Lydia Crew
33 | # --------------
34 |
35 | dn: cn=Horatio Hornblower,ou=people,o=sevenSeas
36 | objectclass: person
37 | objectclass: organizationalPerson
38 | objectclass: inetOrgPerson
39 | objectclass: top
40 | cn: Horatio Hornblower
41 | description: Capt. Horatio Hornblower, R.N
42 | givenname: Horatio
43 | sn: Hornblower
44 | uid: hhornblo
45 | mail: hhornblo@royalnavy.mod.uk
46 | userpassword: {SHA}nU4eI71bcnBGqeO0t9tXvY1u5oQ=
47 |
48 | dn: cn=William Bush,ou=people,o=sevenSeas
49 | objectclass: person
50 | objectclass: organizationalPerson
51 | objectclass: inetOrgPerson
52 | objectclass: top
53 | cn: William Bush
54 | description: Lt. William Bush
55 | givenname: William
56 | manager: cn=Horatio Hornblower,ou=people,o=sevenSeas
57 | sn: Bush
58 | uid: wbush
59 | mail: wbush@royalnavy.mod.uk
60 | userpassword: {SHA}nU4eI71bcnBGqeO0t9tXvY1u5oQ=
61 |
62 | dn: cn=Thomas Quist,ou=people,o=sevenSeas
63 | objectclass: person
64 | objectclass: organizationalPerson
65 | objectclass: inetOrgPerson
66 | objectclass: top
67 | cn: Thomas Quist
68 | description: Seaman Quist
69 | givenname: Thomas
70 | manager: cn=Horatio Hornblower,ou=people,o=sevenSeas
71 | sn: Quist
72 | uid: tquist
73 | mail: tquist@royalnavy.mod.uk
74 | userpassword: {SHA}nU4eI71bcnBGqeO0t9tXvY1u5oQ=
75 |
76 | dn: cn=Moultrie Crystal,ou=people,o=sevenSeas
77 | objectclass: person
78 | objectclass: organizationalPerson
79 | objectclass: inetOrgPerson
80 | objectclass: top
81 | cn: Moultrie Crystal
82 | description: Lt. Crystal
83 | givenname: Moultrie
84 | manager: cn=Horatio Hornblower,ou=people,o=sevenSeas
85 | sn: Crystal
86 | uid: mchrysta
87 | mail: mchrysta@royalnavy.mod.uk
88 | userpassword: {SHA}nU4eI71bcnBGqeO0t9tXvY1u5oQ=
89 |
90 | dn: cn=HMS Lydia,ou=crews,ou=groups,o=sevenSeas
91 | objectclass: groupOfUniqueNames
92 | objectclass: top
93 | cn: HMS Lydia
94 | uniquemember: cn=Horatio Hornblower,ou=people,o=sevenSeas
95 | uniquemember: cn=William Bush,ou=people,o=sevenSeas
96 | uniquemember: cn=Thomas Quist,ou=people,o=sevenSeas
97 | uniquemember: cn=Moultrie Crystal,ou=people,o=sevenSeas
98 |
99 | # HMS Victory Crew
100 | # ----------------
101 |
102 | dn: cn=Horatio Nelson,ou=people,o=sevenSeas
103 | objectclass: person
104 | objectclass: organizationalPerson
105 | objectclass: inetOrgPerson
106 | objectclass: top
107 | cn: Horatio Nelson
108 | description: Lord Horatio Nelson
109 | givenname: Horatio
110 | sn: Nelson
111 | uid: hnelson
112 | mail: hnelson@royalnavy.mod.uk
113 | userpassword: {SHA}nU4eI71bcnBGqeO0t9tXvY1u5oQ=
114 |
115 | dn: cn=Thomas Masterman Hardy,ou=people,o=sevenSeas
116 | objectclass: person
117 | objectclass: organizationalPerson
118 | objectclass: inetOrgPerson
119 | objectclass: top
120 | cn: Thomas Masterman Hardy
121 | description: Sir Thomas Masterman Hardy
122 | givenname: Thomas
123 | manager: cn=Horatio Nelson,ou=people,o=sevenSeas
124 | sn: Hardy
125 | uid: thardy
126 | mail: thardy@royalnavy.mod.uk
127 | userpassword: {SHA}nU4eI71bcnBGqeO0t9tXvY1u5oQ=
128 |
129 | dn: cn=Cornelius Buckley,ou=people,o=sevenSeas
130 | objectclass: person
131 | objectclass: organizationalPerson
132 | objectclass: inetOrgPerson
133 | objectclass: top
134 | cn: Cornelius Buckley
135 | description: LM Cornelius Buckley
136 | givenname: Cornelius
137 | manager: cn=Horatio Nelson,ou=people,o=sevenSeas
138 | sn: Buckley
139 | uid: cbuckley
140 | mail: cbuckley@royalnavy.mod.uk
141 | userpassword: {SHA}nU4eI71bcnBGqeO0t9tXvY1u5oQ=
142 |
143 | dn: cn=HMS Victory,ou=crews,ou=groups,o=sevenSeas
144 | objectclass: groupOfUniqueNames
145 | objectclass: top
146 | cn: HMS Victory
147 | uniquemember: cn=Horatio Nelson,ou=people,o=sevenSeas
148 | uniquemember: cn=Thomas Masterman Hardy,ou=people,o=sevenSeas
149 | uniquemember: cn=Cornelius Buckley,ou=people,o=sevenSeas
150 |
151 | # HMS Bounty Crew
152 | # ---------------
153 |
154 | dn: cn=William Bligh,ou=people,o=sevenSeas
155 | objectclass: person
156 | objectclass: organizationalPerson
157 | objectclass: inetOrgPerson
158 | objectclass: top
159 | cn: William Bligh
160 | description: Captain William Bligh
161 | givenname: William
162 | sn: Bligh
163 | uid: wbligh
164 | mail: wbligh@royalnavy.mod.uk
165 | userpassword: {SHA}nU4eI71bcnBGqeO0t9tXvY1u5oQ=
166 |
167 | dn: cn=Fletcher Christian,ou=people,o=sevenSeas
168 | objectclass: person
169 | objectclass: organizationalPerson
170 | objectclass: inetOrgPerson
171 | objectclass: top
172 | cn: Fletcher Christian
173 | description: Lieutenant Fletcher Christian
174 | givenname: Fletcher
175 | manager: cn=William Bligh,ou=people,o=sevenSeas
176 | sn: Christian
177 | uid: fchristi
178 | mail: fchristi@royalnavy.mod.uk
179 | userpassword: {SHA}nU4eI71bcnBGqeO0t9tXvY1u5oQ=
180 |
181 | dn: cn=John Fryer,ou=people,o=sevenSeas
182 | objectclass: person
183 | objectclass: organizationalPerson
184 | objectclass: inetOrgPerson
185 | objectclass: top
186 | cn: John Fryer
187 | description: Master John Fryer
188 | givenname: John
189 | manager: cn=William Bligh,ou=people,o=sevenSeas
190 | sn: Fryer
191 | uid: jfryer
192 | mail: jfryer@royalnavy.mod.uk
193 | userpassword: {SHA}nU4eI71bcnBGqeO0t9tXvY1u5oQ=
194 |
195 | dn: cn=John Hallett,ou=people,o=sevenSeas
196 | objectclass: person
197 | objectclass: organizationalPerson
198 | objectclass: inetOrgPerson
199 | objectclass: top
200 | cn: John Hallett
201 | description: Midshipman John Hallett
202 | givenname: John
203 | manager: cn=William Bligh,ou=people,o=sevenSeas
204 | sn: Hallett
205 | uid: jhallett
206 | mail: jhallett@royalnavy.mod.uk
207 | userpassword: {SHA}nU4eI71bcnBGqeO0t9tXvY1u5oQ=
208 |
209 | dn: cn=HMS Bounty,ou=crews,ou=groups,o=sevenSeas
210 | objectclass: groupOfUniqueNames
211 | objectclass: top
212 | cn: HMS Bounty
213 | uniquemember: cn=William Bligh,ou=people,o=sevenSeas
214 | uniquemember: cn=Fletcher Christian,ou=people,o=sevenSeas
215 | uniquemember: cn=John Fryer,ou=people,o=sevenSeas
216 | uniquemember: cn=John Hallett,ou=people,o=sevenSeas
217 |
218 |
219 |
220 |
--------------------------------------------------------------------------------
/step-01/images/ldap-00-create-server.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Adobe-Marketing-Cloud/aem-ldap-tutorial/c19b8f0cb1f828eeb311fa12e22e3c9323457ca2/step-01/images/ldap-00-create-server.png
--------------------------------------------------------------------------------
/step-01/images/ldap-01-configure-server.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Adobe-Marketing-Cloud/aem-ldap-tutorial/c19b8f0cb1f828eeb311fa12e22e3c9323457ca2/step-01/images/ldap-01-configure-server.png
--------------------------------------------------------------------------------
/step-01/images/ldap-02-create-partition.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Adobe-Marketing-Cloud/aem-ldap-tutorial/c19b8f0cb1f828eeb311fa12e22e3c9323457ca2/step-01/images/ldap-02-create-partition.png
--------------------------------------------------------------------------------
/step-01/images/ldap-03-start-server.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Adobe-Marketing-Cloud/aem-ldap-tutorial/c19b8f0cb1f828eeb311fa12e22e3c9323457ca2/step-01/images/ldap-03-start-server.png
--------------------------------------------------------------------------------
/step-01/images/ldap-04-create-connection.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Adobe-Marketing-Cloud/aem-ldap-tutorial/c19b8f0cb1f828eeb311fa12e22e3c9323457ca2/step-01/images/ldap-04-create-connection.png
--------------------------------------------------------------------------------
/step-01/images/ldap-05-import-ldiff.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Adobe-Marketing-Cloud/aem-ldap-tutorial/c19b8f0cb1f828eeb311fa12e22e3c9323457ca2/step-01/images/ldap-05-import-ldiff.png
--------------------------------------------------------------------------------
/step-01/images/ldap-06-select-ldiff.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Adobe-Marketing-Cloud/aem-ldap-tutorial/c19b8f0cb1f828eeb311fa12e22e3c9323457ca2/step-01/images/ldap-06-select-ldiff.png
--------------------------------------------------------------------------------
/step-01/images/ldap-07-browse-structure.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Adobe-Marketing-Cloud/aem-ldap-tutorial/c19b8f0cb1f828eeb311fa12e22e3c9323457ca2/step-01/images/ldap-07-browse-structure.png
--------------------------------------------------------------------------------
/step-01/tutorial-01-install-ldap.md:
--------------------------------------------------------------------------------
1 | AEM 6.1 LDAP Authentication Tutorial
2 | ====================================
3 |
4 | Step 01 - Install Apache Directory Server
5 | -----------------------------------------
6 |
7 | In this step we install _Apache Directory Server_ and _Apache Directory Studio_, create and configure a new LDAP server and load it with example data.
8 |
9 | ### Requirements
10 | 1. Apache Directory Server
11 | 2. Apache Directory Studio
12 | 2. Example data: http://directory.apache.org/apacheds/basic-ug/resources/apache-ds-tutorial.ldif
13 |
14 | #### 1. install apache directory server
15 | Folow: http://directory.apache.org/apacheds/basic-ug/1.3-installing-and-starting.html
16 |
17 | #### 2. install apache directory studio
18 | See: http://directory.apache.org/studio/
19 |
20 | #### 3. create a new server
21 | - In directory studio, select the server tab and click the icon for create server.
22 | - choose some meaningful name and click finish
23 | 
24 |
25 | #### 4. configure server
26 | Doubleclick the newly created server to open its configuration.
27 |
28 | - disable anonymous access
29 | - enable access control
30 |
31 | 
32 |
33 | - open _Advanced Partions Configuration_
34 | - delete the _example_ partition
35 | - create new partition:
36 | - for id enter: `SevenSeas`
37 | - for suffix enter: `o=SevenSeas`
38 |
39 | 
40 |
41 | **Save the configuration !!**
42 |
43 | #### 5. start the server
44 | Click on the "Start" in the servers tab
45 |
46 | 
47 |
48 | #### 6. create connection
49 | Right click the server and select _Create a Connection_
50 |
51 | 
52 |
53 | #### 7. connect to server
54 | Double click on the newly created connection in order to connect to the server.
55 |
56 | #### 8. import ldif
57 | Import the example data:
58 |
59 | - right click on the _o=SevenSeas_ node
60 | - select: _Import_ -> _LDIF Import..._
61 |
62 | 
63 |
64 | - choose the `apache-ds-tutorial.ldif` file and click _Finish_
65 |
66 | 
67 |
68 | #### 9. browse structure
69 | You can verify the newly imported entries by browsing the structure below the _o=SevenSeas_ node.
70 |
71 | 
72 |
73 |
--------------------------------------------------------------------------------
/step-02/images/aem-00-setup-logging.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Adobe-Marketing-Cloud/aem-ldap-tutorial/c19b8f0cb1f828eeb311fa12e22e3c9323457ca2/step-02/images/aem-00-setup-logging.png
--------------------------------------------------------------------------------
/step-02/images/aem-01-find-ldap-idp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Adobe-Marketing-Cloud/aem-ldap-tutorial/c19b8f0cb1f828eeb311fa12e22e3c9323457ca2/step-02/images/aem-01-find-ldap-idp.png
--------------------------------------------------------------------------------
/step-02/images/aem-02-configure-ldap-idp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Adobe-Marketing-Cloud/aem-ldap-tutorial/c19b8f0cb1f828eeb311fa12e22e3c9323457ca2/step-02/images/aem-02-configure-ldap-idp.png
--------------------------------------------------------------------------------
/step-02/images/aem-03-find-synchandler.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Adobe-Marketing-Cloud/aem-ldap-tutorial/c19b8f0cb1f828eeb311fa12e22e3c9323457ca2/step-02/images/aem-03-find-synchandler.png
--------------------------------------------------------------------------------
/step-02/images/aem-04-configure-synchandler.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Adobe-Marketing-Cloud/aem-ldap-tutorial/c19b8f0cb1f828eeb311fa12e22e3c9323457ca2/step-02/images/aem-04-configure-synchandler.png
--------------------------------------------------------------------------------
/step-02/images/aem-05-find-loginmodule.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Adobe-Marketing-Cloud/aem-ldap-tutorial/c19b8f0cb1f828eeb311fa12e22e3c9323457ca2/step-02/images/aem-05-find-loginmodule.png
--------------------------------------------------------------------------------
/step-02/images/aem-06-configure-loginmodule.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Adobe-Marketing-Cloud/aem-ldap-tutorial/c19b8f0cb1f828eeb311fa12e22e3c9323457ca2/step-02/images/aem-06-configure-loginmodule.png
--------------------------------------------------------------------------------
/step-02/images/aem-07-jaas-console.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Adobe-Marketing-Cloud/aem-ldap-tutorial/c19b8f0cb1f828eeb311fa12e22e3c9323457ca2/step-02/images/aem-07-jaas-console.png
--------------------------------------------------------------------------------
/step-02/ldap-first-config-pkg.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Adobe-Marketing-Cloud/aem-ldap-tutorial/c19b8f0cb1f828eeb311fa12e22e3c9323457ca2/step-02/ldap-first-config-pkg.zip
--------------------------------------------------------------------------------
/step-02/tutorial-02-configure-aem.md:
--------------------------------------------------------------------------------
1 | AEM 6.1 LDAP Authentication Tutorial
2 | ====================================
3 |
4 | Step 02 - Configure LDAP Authentication in AEM
5 | ----------------------------------------------
6 |
7 | In this step we configure AEM 6.1 to use our newly created LDAP server for login.
8 |
9 | **!** If you want to fast forward, here's the [ldap-first-config-pkg.zip](ldap-first-config-pkg.zip) package with the resulting configuration of this step.
10 |
11 | ### Requirements
12 | 1. LDAP Server with example data (preferably from previous step 01)
13 | 2. Running vanilla AEM 6.1 installation
14 |
15 | #### 1. configure logging
16 | It is always advisable to configure LDAP related logging during initial setup.
17 |
18 | - open [Sling Log Support](http://localhost:4502/system/console/slinglog) in the Felix console
19 | - add `org.apache.jackrabbit.oak.spi.security.authentication.external` as category for controlling the logging of the external login module and sync handler
20 | - add `org.apache.jackrabbit.oak.security.authentication.ldap` as category for controlling the logging of the LDAP identity provider.
21 | - optionally, add `org.apache.directory` as a category for very lowlevel logging of the ldap client calls.
22 |
23 | 
24 |
25 | #### 2. configure LDAP identity provider
26 | Now we configure the LDAP idp. this is the piece that connects to the LDAP server.
27 |
28 | - open the [Felix Configuration Manager](http://localhost:4502/system/console/configMgr) and search for the _"ldap identity provider"_ factory config and click on the plus **+** button.
29 |
30 | 
31 |
32 | Enter the following information:
33 |
34 | | Name | Value |
35 | |------------------------------|-----------------
36 | | LDAP Provider Name | `ldap`
37 | | LDAP Server Hostname | `localhost`
38 | | LDAP Server Port | 10389
39 | | Use SSL | _false_
40 | | Use TLS | _false_
41 | | Disable certificate checking | _false_
42 | | Bind DN | `uid=admin,ou=system`
43 | | Bind Password | `secret`
44 | | Search Timeout | `60s`
45 | | Admin pool max active | 8
46 | | User pool max active | 8
47 | | User base DN | `ou=people,o=SevenSeas`
48 | | User object classes | `person`
49 | | User id attribute | `uid`
50 | | User extra filter |
51 | | User DN paths | _false_
52 | | Group base DN | `ou=groups,o=SevenSeas`
53 | | Group object classes | `groupOfUniqueNames`
54 | | Group name attribute | `cn`
55 | | Group extra filter |
56 | | Group DN paths | _false_
57 | | Group member attribute | `uniquemember`
58 |
59 | And save the config.
60 |
61 | 
62 |
63 | #### 3. configure Default Sync Handler
64 | The sync handler is responsible to synchronize the external users with the local repository.
65 |
66 | - open the [Felix Configuration Manager](http://localhost:4502/system/console/configMgr) and search for the _"Default Sync Handler"_ factory config and click on the plus **+** button.
67 |
68 | 
69 |
70 | Enter the following information:
71 |
72 | | Name | Value
73 | |-------------------------------|--------------------
74 | | Sync Handler Name | `default`
75 | | User Expiration Time | `1h`
76 | | User auto membership | `contributor`
77 | | User property mapping | `rep:fullname=cn`
78 | | User Path Prefix | `/ldap_seven_seas`
79 | | User Membership Expiration | `1h`
80 | | User membership nesting depth | `1`
81 | | Group Expiration Time | `1d`
82 | | Group auto membership |
83 | | Group property mapping |
84 | | Group Path Prefix | `/ldap_seven_seas`
85 |
86 | And save the config
87 |
88 | 
89 |
90 | #### 4. configure the external login module
91 | The external login module is the bridge between the login, the idp and the sync handler.
92 |
93 | - open the [Felix Configuration Manager](http://localhost:4502/system/console/configMgr) and search for the _"External Login Module"_ factory config and click on the plus **+** button.
94 |
95 | 
96 |
97 | Enter the following information:
98 |
99 | | Name | Value
100 | |------------------------|----------
101 | | JAAS Ranking | 50
102 | | JAAS Control Flag | `SUFFICIENT`
103 | | JAAS Realm |
104 | | Identity Provider Name | `ldap`
105 | | Sync Handler Name | `default`
106 |
107 | And save the config
108 |
109 | 
110 |
111 | #### 5. verify the JAAS console
112 | The external login module should now show up in the JAAS console:
113 |
114 | - open the [Felix JAAS Console](http://localhost:4502/system/console/jaas)
115 |
116 | 
117 |
118 |
119 |
120 |
121 |
--------------------------------------------------------------------------------
/step-03/images/test-01-useradmin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Adobe-Marketing-Cloud/aem-ldap-tutorial/c19b8f0cb1f828eeb311fa12e22e3c9323457ca2/step-03/images/test-01-useradmin.png
--------------------------------------------------------------------------------
/step-03/images/test-02-newconfig.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Adobe-Marketing-Cloud/aem-ldap-tutorial/c19b8f0cb1f828eeb311fa12e22e3c9323457ca2/step-03/images/test-02-newconfig.png
--------------------------------------------------------------------------------
/step-03/images/test-03-useradmin-withprops.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Adobe-Marketing-Cloud/aem-ldap-tutorial/c19b8f0cb1f828eeb311fa12e22e3c9323457ca2/step-03/images/test-03-useradmin-withprops.png
--------------------------------------------------------------------------------
/step-03/images/test-04-jmx-console.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Adobe-Marketing-Cloud/aem-ldap-tutorial/c19b8f0cb1f828eeb311fa12e22e3c9323457ca2/step-03/images/test-04-jmx-console.png
--------------------------------------------------------------------------------
/step-03/images/test-05-jmx-synchandler.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Adobe-Marketing-Cloud/aem-ldap-tutorial/c19b8f0cb1f828eeb311fa12e22e3c9323457ca2/step-03/images/test-05-jmx-synchandler.png
--------------------------------------------------------------------------------
/step-03/images/test-06-jmx-syncexternalusers.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Adobe-Marketing-Cloud/aem-ldap-tutorial/c19b8f0cb1f828eeb311fa12e22e3c9323457ca2/step-03/images/test-06-jmx-syncexternalusers.png
--------------------------------------------------------------------------------
/step-03/images/test-07-useradmin-more.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Adobe-Marketing-Cloud/aem-ldap-tutorial/c19b8f0cb1f828eeb311fa12e22e3c9323457ca2/step-03/images/test-07-useradmin-more.png
--------------------------------------------------------------------------------
/step-03/ldap-config-with-profile-pkg.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Adobe-Marketing-Cloud/aem-ldap-tutorial/c19b8f0cb1f828eeb311fa12e22e3c9323457ca2/step-03/ldap-config-with-profile-pkg.zip
--------------------------------------------------------------------------------
/step-03/log-snip-01.md:
--------------------------------------------------------------------------------
1 | Log snippet during initial successfull login
2 | --------------------------------------------
3 |
4 | ````
5 | 22.07.2015 16:41:08.186 *DEBUG* [qtp1188867090-847] org.apache.jackrabbit.oak.security.authentication.ldap.impl.LdapIdentityProvider search below ou=people,o=SevenSeas with (&(uid=wbush)(objectclass=person)) found cn=William Bush,ou=people,o=SevenSeas
6 | 22.07.2015 16:41:08.186 *DEBUG* [qtp1188867090-847] org.apache.jackrabbit.oak.security.authentication.ldap.impl.LdapIdentityProvider getUser(wbush) (connect=1.92ms, lookup=3.03ms)
7 | 22.07.2015 16:41:08.186 *DEBUG* [qtp1188867090-847] org.apache.jackrabbit.oak.security.authentication.ldap.impl.PoolableUnboundConnectionFactory activate connection: org.apache.directory.ldap.client.api.LdapNetworkConnection@3aa49064
8 | 22.07.2015 16:41:08.188 *DEBUG* [qtp1188867090-847] org.apache.jackrabbit.oak.security.authentication.ldap.impl.PoolableUnboundConnectionFactory validating connection org.apache.directory.ldap.client.api.LdapNetworkConnection@3aa49064: true
9 | 22.07.2015 16:41:08.196 *DEBUG* [qtp1188867090-847] org.apache.jackrabbit.oak.security.authentication.ldap.impl.LdapIdentityProvider authenticate(wbush) (connect=1.61ms, bind=8.30ms)
10 | 22.07.2015 16:41:08.196 *DEBUG* [qtp1188867090-847] org.apache.jackrabbit.oak.security.authentication.ldap.impl.PoolableUnboundConnectionFactory passivate connection: org.apache.directory.ldap.client.api.LdapNetworkConnection@3aa49064
11 | 22.07.2015 16:41:08.196 *DEBUG* [qtp1188867090-847] org.apache.jackrabbit.oak.spi.security.authentication.external.impl.ExternalLoginModule IDP ldap returned valid user LdapIdentity{ref=ExternalIdentityRef{id='cn=William Bush,ou=people,o=SevenSeas', providerName='ldap'}, id='wbush'}
12 | 22.07.2015 16:41:08.202 *DEBUG* [qtp1188867090-847] org.apache.jackrabbit.oak.spi.security.authentication.external.impl.DefaultSyncHandler Properties of user 'wbush' need sync. rep:lastSynced not set.
13 | 22.07.2015 16:41:08.203 *DEBUG* [qtp1188867090-847] org.apache.jackrabbit.oak.spi.security.authentication.external.impl.DefaultSyncHandler Membership of user 'wbush' need sync. rep:lastSynced not set.
14 | 22.07.2015 16:41:08.203 *DEBUG* [qtp1188867090-847] org.apache.jackrabbit.oak.spi.security.authentication.external.impl.DefaultSyncHandler Syncing membership 'cn=William Bush,ou=people,o=SevenSeas;ldap' -> 'wbush'
15 | 22.07.2015 16:41:08.210 *DEBUG* [qtp1188867090-847] org.apache.jackrabbit.oak.security.authentication.ldap.impl.LdapIdentityProvider search below ou=groups,o=SevenSeas with (&(uniquemember=cn=William Bush,ou=people,o=SevenSeas)(objectclass=groupOfUniqueNames)) found 1 entries. (connect=2.34ms, search=226.00us, iterate=3.28ms)
16 | 22.07.2015 16:41:08.211 *DEBUG* [qtp1188867090-847] org.apache.jackrabbit.oak.spi.security.authentication.external.impl.DefaultSyncHandler - processing membership cn=HMS Lydia,ou=crews,ou=groups,o=SevenSeas
17 | 22.07.2015 16:41:08.215 *DEBUG* [qtp1188867090-847] org.apache.jackrabbit.oak.spi.security.authentication.external.impl.DefaultSyncHandler - idp returned 'HMS Lydia'
18 | 22.07.2015 16:41:08.216 *DEBUG* [qtp1188867090-847] org.apache.jackrabbit.oak.spi.security.authentication.external.impl.DefaultSyncHandler - user manager returned 'null'
19 | 22.07.2015 16:41:08.217 *DEBUG* [qtp1188867090-847] org.apache.jackrabbit.oak.spi.security.authentication.external.impl.DefaultSyncHandler - created new group
20 | 22.07.2015 16:41:08.217 *DEBUG* [qtp1188867090-847] org.apache.jackrabbit.oak.spi.security.authentication.external.impl.DefaultSyncHandler Properties of group 'HMS Lydia' need sync. rep:lastSynced not set.
21 | 22.07.2015 16:41:08.218 *DEBUG* [qtp1188867090-847] org.apache.jackrabbit.oak.spi.security.authentication.external.impl.DefaultSyncHandler - added 'User 'wbush'' as member to 'Group 'HMS Lydia''
22 | 22.07.2015 16:41:08.218 *DEBUG* [qtp1188867090-847] org.apache.jackrabbit.oak.spi.security.authentication.external.impl.DefaultSyncHandler - group nesting level for 'HMS Lydia' reached
23 | 22.07.2015 16:41:08.219 *DEBUG* [qtp1188867090-847] org.apache.jackrabbit.oak.spi.security.authentication.external.impl.DefaultSyncHandler syncMembership(wbush) (fetching=6.26ms, reading=1.27ms, adding=7.41ms, removing=25.00us)
24 | 22.07.2015 16:41:08.219 *DEBUG* [qtp1188867090-847] org.apache.jackrabbit.oak.spi.security.authentication.external.impl.DefaultSyncHandler sync(cn=William Bush,ou=people,o=SevenSeas;ldap) -> wbush (find=848.00us, create=2.03ms, sync=16.78ms)
25 | 22.07.2015 16:41:08.223 *DEBUG* [qtp1188867090-847] org.apache.jackrabbit.oak.spi.security.authentication.external.impl.ExternalLoginModule syncUser(wbush) (sync=21.99ms, commit=4.05ms)
26 | ````
27 |
28 | Log snippet when user (foo) does not exist:
29 | -------------------------------------
30 | ````
31 | 22.07.2015 17:00:30.029 *DEBUG* [qtp1188867090-843] org.apache.jackrabbit.oak.security.authentication.ldap.impl.LdapIdentityProvider search below ou=people,o=SevenSeas with (&(uid=foo)(objectclass=person)) found 0 entries.
32 | 22.07.2015 17:00:30.029 *DEBUG* [qtp1188867090-843] org.apache.jackrabbit.oak.security.authentication.ldap.impl.LdapIdentityProvider getUser(foo) (connect=1.88ms, lookup=3.31ms)
33 | 22.07.2015 17:00:30.029 *DEBUG* [qtp1188867090-843] org.apache.jackrabbit.oak.spi.security.authentication.external.impl.ExternalLoginModule IDP ldap returned null for simple creds of foo
34 | ````
35 |
36 | Log snippet for invalid password:
37 | ---------------------------------
38 | ````
39 | 22.07.2015 17:02:04.886 *DEBUG* [qtp1188867090-843] org.apache.jackrabbit.oak.security.authentication.ldap.impl.LdapIdentityProvider search below ou=people,o=SevenSeas with (&(uid=wbush)(objectclass=person)) found cn=William Bush,ou=people,o=SevenSeas
40 | 22.07.2015 17:02:04.886 *DEBUG* [qtp1188867090-843] org.apache.jackrabbit.oak.security.authentication.ldap.impl.LdapIdentityProvider getUser(wbush) (connect=1.79ms, lookup=2.97ms)
41 | 22.07.2015 17:02:04.886 *DEBUG* [qtp1188867090-843] org.apache.jackrabbit.oak.security.authentication.ldap.impl.PoolableUnboundConnectionFactory activate connection: org.apache.directory.ldap.client.api.LdapNetworkConnection@3aa49064
42 | 22.07.2015 17:02:04.887 *DEBUG* [qtp1188867090-843] org.apache.jackrabbit.oak.security.authentication.ldap.impl.PoolableUnboundConnectionFactory validating connection org.apache.directory.ldap.client.api.LdapNetworkConnection@3aa49064: true
43 | 22.07.2015 17:02:04.894 *DEBUG* [qtp1188867090-843] org.apache.jackrabbit.oak.security.authentication.ldap.impl.PoolableUnboundConnectionFactory passivate connection: org.apache.directory.ldap.client.api.LdapNetworkConnection@3aa49064
44 | 22.07.2015 17:02:04.894 *DEBUG* [qtp1188867090-843] org.apache.jackrabbit.oak.spi.security.authentication.external.impl.ExternalLoginModule IDP ldap throws login exception for 'wbush': Unable to authenticate against LDAP server: INVALID_CREDENTIALS: Bind failed: ERR_229 Cannot authenticate user cn=William Bush,ou=people,o=SevenSeas:
45 | org.apache.directory.api.ldap.model.exception.LdapAuthenticationException: ERR_229 Cannot authenticate user cn=William Bush,ou=people,o=SevenSeas
46 | at org.apache.directory.server.core.authn.AuthenticationInterceptor.bind(AuthenticationInterceptor.java:671)
47 | at org.apache.directory.server.core.DefaultOperationManager.bind(DefaultOperationManager.java:439)
48 | at org.apache.directory.server.ldap.handlers.request.BindRequestHandler.handleSimpleAuth(BindRequestHandler.java:184)
49 | at org.apache.directory.server.ldap.handlers.request.BindRequestHandler.handle(BindRequestHandler.java:636)
50 | at org.apache.directory.server.ldap.handlers.request.BindRequestHandler.handle(BindRequestHandler.java:66)
51 | at org.apache.directory.server.ldap.handlers.LdapRequestHandler.handleMessage(LdapRequestHandler.java:193)
52 | at org.apache.directory.server.ldap.handlers.LdapRequestHandler.handleMessage(LdapRequestHandler.java:56)
53 | at org.apache.mina.handler.demux.DemuxingIoHandler.messageReceived(DemuxingIoHandler.java:221)
54 | at org.apache.directory.server.ldap.LdapProtocolHandler.messageReceived(LdapProtocolHandler.java:217)
55 | at org.apache.mina.core.filterchain.DefaultIoFilterChain$TailFilter.messageReceived(DefaultIoFilterChain.java:854)
56 | at org.apache.mina.core.filterchain.DefaultIoFilterChain.callNextMessageReceived(DefaultIoFilterChain.java:542)
57 | at org.apache.mina.core.filterchain.DefaultIoFilterChain.access$1300(DefaultIoFilterChain.java:48)
58 | at org.apache.mina.core.filterchain.DefaultIoFilterChain$EntryImpl$1.messageReceived(DefaultIoFilterChain.java:943)
59 | at org.apache.mina.core.filterchain.IoFilterEvent.fire(IoFilterEvent.java:74)
60 | at org.apache.mina.core.session.IoEvent.run(IoEvent.java:63)
61 | at org.apache.mina.filter.executor.UnorderedThreadPoolExecutor$Worker.runTask(UnorderedThreadPoolExecutor.java:475)
62 | at org.apache.mina.filter.executor.UnorderedThreadPoolExecutor$Worker.run(UnorderedThreadPoolExecutor.java:429)
63 | at java.lang.Thread.run(Thread.java:744)
64 |
65 |
66 | BindRequest =
67 | MessageType : BIND_REQUEST
68 | Message ID : 16
69 | BindRequest
70 | Version : '3'
71 | Name : 'cn=William Bush,ou=people,o=SevenSeas'
72 | Simple authentication : '(omitted-for-safety)'
73 |
74 | 22.07.2015 17:02:04.894 *INFO* [qtp1188867090-843] org.apache.sling.auth.core.impl.SlingAuthenticator handleLoginFailure: Unable to authenticate null: UserId/Password mismatch.
75 | ````
76 |
77 | Log snippet when user is resynced
78 | ---------------------------------
79 | ````
80 | 22.07.2015 17:27:26.663 *DEBUG* [qtp1188867090-1077] org.apache.jackrabbit.oak.security.authentication.ldap.impl.LdapIdentityProvider search below ou=people,o=SevenSeas with (&(uid=wbush)(objectclass=person)) found cn=William Bush,ou=people,o=SevenSeas
81 | 22.07.2015 17:27:26.664 *DEBUG* [qtp1188867090-1077] org.apache.jackrabbit.oak.security.authentication.ldap.impl.LdapIdentityProvider getUser(wbush) (connect=1.79ms, lookup=2.87ms)
82 | 22.07.2015 17:27:26.664 *DEBUG* [qtp1188867090-1077] org.apache.jackrabbit.oak.security.authentication.ldap.impl.PoolableUnboundConnectionFactory activate connection: org.apache.directory.ldap.client.api.LdapNetworkConnection@3aa49064
83 | 22.07.2015 17:27:26.665 *DEBUG* [qtp1188867090-1077] org.apache.jackrabbit.oak.security.authentication.ldap.impl.PoolableUnboundConnectionFactory validating connection org.apache.directory.ldap.client.api.LdapNetworkConnection@3aa49064: true
84 | 22.07.2015 17:27:26.668 *DEBUG* [qtp1188867090-1077] org.apache.jackrabbit.oak.security.authentication.ldap.impl.LdapIdentityProvider authenticate(wbush) (connect=1.46ms, bind=2.83ms)
85 | 22.07.2015 17:27:26.668 *DEBUG* [qtp1188867090-1077] org.apache.jackrabbit.oak.security.authentication.ldap.impl.PoolableUnboundConnectionFactory passivate connection: org.apache.directory.ldap.client.api.LdapNetworkConnection@3aa49064
86 | 22.07.2015 17:27:26.668 *DEBUG* [qtp1188867090-1077] org.apache.jackrabbit.oak.spi.security.authentication.external.impl.ExternalLoginModule IDP ldap returned valid user LdapIdentity{ref=ExternalIdentityRef{id='cn=William Bush,ou=people,o=SevenSeas', providerName='ldap'}, id='wbush'}
87 | 22.07.2015 17:27:26.669 *DEBUG* [qtp1188867090-1077] org.apache.jackrabbit.oak.spi.security.authentication.external.impl.DefaultSyncHandler Properties of user 'wbush' need sync. rep:lastSynced expired (2113690 > 10000)
88 | 22.07.2015 17:27:26.670 *DEBUG* [qtp1188867090-1077] org.apache.jackrabbit.oak.spi.security.authentication.external.impl.DefaultSyncHandler Membership of user 'wbush' do not need sync.
89 | 22.07.2015 17:27:26.670 *DEBUG* [qtp1188867090-1077] org.apache.jackrabbit.oak.spi.security.authentication.external.impl.DefaultSyncHandler sync(cn=William Bush,ou=people,o=SevenSeas;ldap) -> wbush (find=436.00us, sync=1.24ms)
90 | 22.07.2015 17:27:26.671 *DEBUG* [qtp1188867090-1077] org.apache.jackrabbit.oak.spi.security.authentication.external.impl.ExternalLoginModule syncUser(wbush) (sync=1.79ms, commit=826.00us)
91 | ````
92 |
93 |
--------------------------------------------------------------------------------
/step-03/tutorial-03-test.md:
--------------------------------------------------------------------------------
1 | AEM 6.1 LDAP Authentication Tutorial
2 | ====================================
3 |
4 | Step 03 - Test that it works!
5 | -----------------------------
6 |
7 | In this step we verify that the LDAP authentication works and if the users are synced properly.
8 |
9 | **!** If you want to fast forward, here's the [ldap-config-with-profile-pkg.zip](ldap-config-with-profile-pkg.zip) package with the resulting configuration of this step.
10 |
11 |
12 | ### Requirements
13 | 1. LDAP Server with example data (from step 01)
14 | 2. AEM 6.1 installation with configured LDAP authentication (from step 02)
15 |
16 | #### 1. simple test - login as a ldap user
17 | The first test is to login as a user that does not exist in the repository but only on the ldap.
18 |
19 | - ensure to logout previous session or clear all browser cookie or use a different browser, hostname or IP
20 | - open browser to aem: http://127.0.0.1:4502/
21 | - login as `wbush` with password `pass`
22 |
23 | if the login succeeds, you should now see the authoring environment because we configured the `user.autoMembership` to include the `contributor` group.
24 |
25 | Looking at the users and groups should show the user _William Bush_ and his group:
26 |
27 | - open the [AEM useradmin](http://localhost:4502/useradmin)
28 | - search for `seven`
29 |
30 | 
31 |
32 | If you look at the log files, you should see something like this here: [log-snip-01.md](log-snip-01.md)
33 |
34 | #### 2. add more config for first- and givenname
35 | As you can see in the AEM user admin, the fields for _First Name_ and _Last Name_ are empty, because the AEM useradmin uses the `profile/givenName` and `profile/familyName` properties.
36 |
37 | So let's alter the config so that this information is populated as well.
38 |
39 | - open the [Felix Configuration Manager](http://localhost:4502/system/console/configMgr) and search for the _"Default Sync Handler"_ factory config and click on the first confg to edit it
40 |
41 | - change the value for _User property mapping_ and add the mappings.
42 | - also lower the value for _User expiration time_ so we can see the effects sooner
43 |
44 |
45 | Enter the following information:
46 |
47 | | Name | Value
48 | |-------------------------------|--------------------
49 | | User Expiration Time | `10s`
50 | | User property mapping | `rep:fullname=cn`
`profile/nt:primaryType="nt:unstructured"`
`profile/givenName=givenname`
`profile/familyName=sn` |
51 |
52 | 
53 |
54 | And save the config.
55 |
56 | Now, if you logout and login again with `wbush` the user should be resynced.
57 |
58 | 
59 |
60 | #### 3. use JMX console to sync more users
61 | Oak comes with a default JMX mbean that allows you to control the sync handler.
62 |
63 | - open the [Felix JMX Console](http://localhost:4502/system/console/jmx) and search for the _"External Identity"_ bean and select it
64 |
65 | 
66 |
67 | 
68 |
69 | - click on `syncAllExternalUsers()` and then on `invoke`. this will collect all the users from the IDP and sync them with the repository. you will see an `add` _op_ property for all newly added users, and an `upd` for the updated ones.
70 |
71 | 
72 |
73 | checking back the user admin shows the newly imported users and groups
74 |
75 | 
76 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/step-04/authorizables.json:
--------------------------------------------------------------------------------
1 | {
2 | "enterprise": {
3 | "id": "enterprise",
4 | "members": ["kirk", "spock", "mccoy"]
5 | },
6 | "kirk": {
7 | "id": "kirk",
8 | "fullname": "James T. Kirk",
9 | "givenname": "James T.",
10 | "familyname": "Kirk",
11 | "email": "kirk@seven_skies.example.com",
12 | "password": "pass",
13 | "groups": ["enterprise"]
14 | },
15 | "spock": {
16 | "id": "spock",
17 | "fullname": "Spock",
18 | "givenname": "Spock",
19 | "familyname": "",
20 | "email": "spock@seven_skies.example.com",
21 | "password": "pass",
22 | "groups": ["enterprise"]
23 | },
24 | "mccoy": {
25 | "id": "mcccoy",
26 | "fullname": "Leonard McCoy",
27 | "givenname": "Leonard",
28 | "familyname": "McCoy",
29 | "email": "mccoy@seven_skies.example.com",
30 | "password": "pass",
31 | "groups": ["enterprise"]
32 | },
33 |
34 | "voyager": {
35 | "id": "voyager",
36 | "members": ["janeway", "chakotay", "tuvok"]
37 | },
38 | "janeway": {
39 | "id": "janeway",
40 | "fullname": "Kathryn Janeway",
41 | "givenname": "Kathryn",
42 | "familyname": "Janeway",
43 | "email": "janeway@seven_skies.example.com",
44 | "password": "pass",
45 | "groups": ["voyager"]
46 | },
47 | "chakotay": {
48 | "id": "chakotay",
49 | "fullname": "Chakotay",
50 | "givenname": "Chakotay",
51 | "familyname": "",
52 | "email": "chakotay@seven_skies.example.com",
53 | "password": "pass",
54 | "groups": ["voyager"]
55 | },
56 | "tuvok": {
57 | "id": "tuvok",
58 | "fullname": "Tuvok",
59 | "givenname": "Tuvok",
60 | "familyname": "",
61 | "email": "tuvok@seven_skies.example.com",
62 | "password": "pass",
63 | "groups": ["voyager"]
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/step-04/example-idp/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | target
3 |
--------------------------------------------------------------------------------
/step-04/example-idp/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
18 |
20 | 4.0.0
21 |
22 |
23 |
24 |
25 | org.apache.jackrabbit
26 | oak-parent
27 | 1.2.2
28 |
29 |
30 |
31 |
32 |
33 |
34 | com.adobe.gems
35 | com.adobe.gems.exampleidp
36 | 1.0-SNAPSHOT
37 | AEM 6.1 Gems Example IDP
38 | Example IDP that reads users and groups from a json file
39 | bundle
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | org.apache.felix
48 | maven-bundle-plugin
49 |
50 |
51 | org.apache.felix
52 | maven-scr-plugin
53 | 1.20.0
54 |
55 |
56 | org.apache.sling
57 | maven-sling-plugin
58 |
59 | http://localhost:4502/system/console
60 | false
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 | org.apache.jackrabbit
72 | oak-auth-external
73 | 1.2.2
74 | provided
75 |
76 |
77 | org.apache.sling
78 | org.apache.sling.commons.json
79 | 2.0.10
80 | provided
81 |
82 |
83 |
84 | org.slf4j
85 | slf4j-api
86 | provided
87 |
88 |
89 |
90 | org.apache.felix
91 | org.apache.felix.scr.annotations
92 | provided
93 |
94 |
95 | com.google.code.findbugs
96 | jsr305
97 | provided
98 |
99 |
100 |
101 |
--------------------------------------------------------------------------------
/step-04/example-idp/src/main/java/com/adobe/gems/exampleidp/impl/ExternalGroupImpl.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one
3 | * or more contributor license agreements. See the NOTICE file
4 | * distributed with this work for additional information
5 | * regarding copyright ownership. The ASF licenses this file
6 | * to you under the Apache License, Version 2.0 (the
7 | * "License"); you may not use this file except in compliance
8 | * with the License. You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing,
13 | * software distributed under the License is distributed on an
14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | * KIND, either express or implied. See the License for the
16 | * specific language governing permissions and limitations
17 | * under the License.
18 | */
19 | package com.adobe.gems.exampleidp.impl;
20 |
21 | import java.util.HashSet;
22 | import java.util.Map;
23 | import java.util.Set;
24 |
25 | import javax.annotation.Nonnull;
26 |
27 | import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalGroup;
28 | import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityException;
29 | import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityRef;
30 | import org.apache.sling.commons.json.JSONArray;
31 |
32 | /**
33 | * {@code ExternalGroupImpl} implements an external group based on properties
34 | */
35 | public class ExternalGroupImpl extends ExternalIdentityImpl implements ExternalGroup {
36 |
37 | private Set members;
38 |
39 | public ExternalGroupImpl(String providerName, ExternalIdentityRef ref, String id, Map properties) {
40 | super(providerName, ref, id, properties);
41 | }
42 |
43 | @Nonnull
44 | @Override
45 | public Iterable getDeclaredMembers() throws ExternalIdentityException {
46 | if (members == null) {
47 | members = new HashSet();
48 | JSONArray gs = (JSONArray) properties.get(JsonFileIdentityProvider.PN_MEMBERS);
49 | if (gs != null) {
50 | for (int i = 0; i groups;
44 |
45 | protected final Map properties;
46 |
47 | protected ExternalIdentityImpl(String providerName, ExternalIdentityRef ref, String id, Map properties) {
48 | this.providerName = providerName;
49 | this.ref = ref;
50 | this.id = id;
51 | this.properties = properties;
52 | }
53 |
54 | @Nonnull
55 | @Override
56 | public ExternalIdentityRef getExternalId() {
57 | return ref;
58 | }
59 |
60 | @Nonnull
61 | @Override
62 | public String getId() {
63 | return id;
64 | }
65 |
66 | @Nonnull
67 | @Override
68 | public String getPrincipalName() {
69 | return ref.getId();
70 | }
71 |
72 | @Override
73 | public String getIntermediatePath() {
74 | return null;
75 | }
76 |
77 | @Nonnull
78 | @Override
79 | public Iterable getDeclaredGroups() throws ExternalIdentityException {
80 | if (groups == null) {
81 | groups = new HashSet();
82 | JSONArray gs = (JSONArray) properties.get(JsonFileIdentityProvider.PN_GROUPS);
83 | if (gs != null) {
84 | for (int i = 0; i getProperties() {
98 | return properties;
99 | }
100 |
101 | @Override
102 | public String toString() {
103 | return "ExternalIdentityImpl{" + "ref=" + ref + ", id='" + id + '\'' + '}';
104 | }
105 |
106 | }
--------------------------------------------------------------------------------
/step-04/example-idp/src/main/java/com/adobe/gems/exampleidp/impl/ExternalUserImpl.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one
3 | * or more contributor license agreements. See the NOTICE file
4 | * distributed with this work for additional information
5 | * regarding copyright ownership. The ASF licenses this file
6 | * to you under the Apache License, Version 2.0 (the
7 | * "License"); you may not use this file except in compliance
8 | * with the License. You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing,
13 | * software distributed under the License is distributed on an
14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | * KIND, either express or implied. See the License for the
16 | * specific language governing permissions and limitations
17 | * under the License.
18 | */
19 | package com.adobe.gems.exampleidp.impl;
20 |
21 | import java.util.Map;
22 |
23 | import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityRef;
24 | import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalUser;
25 |
26 | /**
27 | * {@code ExternalUserImpl} implements an external user based on properties
28 | */
29 | public class ExternalUserImpl extends ExternalIdentityImpl implements ExternalUser {
30 |
31 | public ExternalUserImpl(String providerName, ExternalIdentityRef ref, String id, Map properties) {
32 | super(providerName, ref, id, properties);
33 | }
34 | }
--------------------------------------------------------------------------------
/step-04/example-idp/src/main/java/com/adobe/gems/exampleidp/impl/JsonFileIdentityProvider.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one
3 | * or more contributor license agreements. See the NOTICE file
4 | * distributed with this work for additional information
5 | * regarding copyright ownership. The ASF licenses this file
6 | * to you under the Apache License, Version 2.0 (the
7 | * "License"); you may not use this file except in compliance
8 | * with the License. You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing,
13 | * software distributed under the License is distributed on an
14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | * KIND, either express or implied. See the License for the
16 | * specific language governing permissions and limitations
17 | * under the License.
18 | */
19 | package com.adobe.gems.exampleidp.impl;
20 |
21 | import java.io.File;
22 | import java.io.FileNotFoundException;
23 | import java.io.IOException;
24 | import java.util.ArrayList;
25 | import java.util.HashMap;
26 | import java.util.Iterator;
27 | import java.util.List;
28 | import java.util.Map;
29 |
30 | import javax.annotation.CheckForNull;
31 | import javax.annotation.Nonnull;
32 | import javax.annotation.Nullable;
33 | import javax.jcr.Credentials;
34 | import javax.jcr.SimpleCredentials;
35 | import javax.security.auth.login.LoginException;
36 |
37 | import org.apache.commons.io.FileUtils;
38 | import org.apache.felix.scr.annotations.Activate;
39 | import org.apache.felix.scr.annotations.Component;
40 | import org.apache.felix.scr.annotations.ConfigurationPolicy;
41 | import org.apache.felix.scr.annotations.Deactivate;
42 | import org.apache.felix.scr.annotations.Property;
43 | import org.apache.felix.scr.annotations.Service;
44 | import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
45 | import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalGroup;
46 | import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentity;
47 | import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityException;
48 | import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityProvider;
49 | import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityRef;
50 | import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalUser;
51 | import org.apache.sling.commons.json.JSONArray;
52 | import org.apache.sling.commons.json.JSONException;
53 | import org.apache.sling.commons.json.JSONObject;
54 | import org.slf4j.Logger;
55 | import org.slf4j.LoggerFactory;
56 |
57 | /**
58 | * {@code JsonFileIdentityProvider} implements an external identity provider that reads users and groups from
59 | * a simple json file. the structure is very simple, using the authorizable id as key. if the object has a 'members' property,
60 | * it is considered a group. all password are just plaintext.
61 | *
62 | * for simpler group membership lookup, we also store the groups in the user objects.
63 | *
64 | * Example:
65 | *
66 | *
67 | * {
68 | * "enterprise": {
69 | * "id": "enterprise",
70 | * "members": ["kirk", "spock"]
71 | * },
72 | * "kirk": {
73 | * "id": "kirk",
74 | * "fullname": "James T. Kirk",
75 | * "password": "pass",
76 | * "groups": ["enterprise"]
77 | * },
78 | * "spock": {
79 | * "id": "spock",
80 | * "fullname": "Spock",
81 | * "password": "pass",
82 | * "groups": ["enterprise"]
83 | * }
84 | * }
85 | *
86 | */
87 | @Component(
88 | label = "JSON File Identity Provider",
89 | configurationFactory = true,
90 | metatype = true,
91 | policy = ConfigurationPolicy.REQUIRE
92 | )
93 | @Service
94 | public class JsonFileIdentityProvider implements ExternalIdentityProvider {
95 |
96 | /**
97 | * property name for members
98 | */
99 | public static final String PN_MEMBERS = "members";
100 |
101 | /**
102 | * property name for groups
103 | */
104 | public static final String PN_GROUPS = "groups";
105 |
106 | /**
107 | * property name for password
108 | */
109 | public static final String PN_PASSWORD = "password";
110 |
111 | /**
112 | * default logger
113 | */
114 | private static final Logger log = LoggerFactory.getLogger(JsonFileIdentityProvider.class);
115 |
116 | /**
117 | * @see #getName()
118 | */
119 | public static final String PARAM_NAME_DEFAULT = "json";
120 |
121 | /**
122 | * @see #getName()
123 | */
124 | @Property(
125 | label = "Provider Name",
126 | description = "Name of this provider configuration. This is used to reference this provider by the login modules.",
127 | value = PARAM_NAME_DEFAULT
128 | )
129 | public static final String PARAM_NAME = "provider.name";
130 |
131 | /**
132 | * The default value of the json file
133 | */
134 | public static final String PARAM_FILE_NAME_DEFAULT = "authorizables.json";
135 |
136 | /**
137 | * The property for the json file
138 | */
139 | @Property(
140 | label = "JSON Filename",
141 | description = "Filename (path) of the json file that stores the user and group information.",
142 | value = PARAM_FILE_NAME_DEFAULT
143 | )
144 | public static final String PARAM_FILE_NAME = "filename";
145 |
146 | /**
147 | * name of this provider
148 | */
149 | private String name;
150 |
151 | /**
152 | * configured filename
153 | */
154 | private String fileName;
155 |
156 | /**
157 | * resolved json file
158 | */
159 | private File jsonFile;
160 |
161 | @SuppressWarnings("UnusedDeclaration")
162 | @Activate
163 | private void activate(Map properties) {
164 | ConfigurationParameters cfg = ConfigurationParameters.of(properties);
165 | name = cfg.getConfigValue(PARAM_NAME, PARAM_NAME_DEFAULT);
166 | fileName = cfg.getConfigValue(PARAM_FILE_NAME, PARAM_FILE_NAME_DEFAULT);
167 | init();
168 | }
169 |
170 | @SuppressWarnings("UnusedDeclaration")
171 | @Deactivate
172 | private void deactivate() {
173 | }
174 |
175 | /**
176 | * Initialized the provider and validates the properties.
177 | */
178 | private void init() {
179 | if (jsonFile == null || !jsonFile.exists()) {
180 | try {
181 | jsonFile = new File(fileName).getCanonicalFile();
182 | log.info("json file IDP initialized. using file: {}", jsonFile.getPath());
183 | } catch (IOException e) {
184 | jsonFile = null;
185 | log.warn("error while initializing json file IDP. ", e);
186 | }
187 | }
188 | }
189 |
190 | /**
191 | * Loads the authorizable JSON.
192 | * @return the JSON object of the data.
193 | * @throws IOException if an error occurrs
194 | */
195 | @Nonnull
196 | private JSONObject loadJSON() throws IOException {
197 | init();
198 | if (jsonFile != null) {
199 | String json = FileUtils.readFileToString(jsonFile, "utf-8");
200 | try {
201 | return new JSONObject(json);
202 | } catch (JSONException e) {
203 | log.error("error while parsing json {}", fileName, e);
204 | throw new IOException("Error while parsing json");
205 | }
206 | } else {
207 | throw new FileNotFoundException("JSON file not found: " + fileName);
208 | }
209 | }
210 |
211 | /**
212 | * Checks if the given identity reference has the same provider name as this one.
213 | * @param ref the reference
214 | * @return {@code true} if the reference originates from this provider.
215 | */
216 | private boolean isMyRef(@Nonnull ExternalIdentityRef ref) {
217 | final String refProviderName = ref.getProviderName();
218 | return refProviderName == null || refProviderName.isEmpty() || getName().equals(refProviderName);
219 | }
220 |
221 | /**
222 | * Creates a new external identity of the given {@code type} or {@code null} if the type does not match the object
223 | * @param id the id
224 | * @param ref the extern reference or {@code null}
225 | * @param obj the json data
226 | * @param type the desired type
227 | * @return the new identity or {@code null}
228 | * @throws JSONException
229 | */
230 | @CheckForNull
231 | private T createIdentity(@Nonnull String id, @Nullable ExternalIdentityRef ref,
232 | @Nullable JSONObject obj, @Nonnull Class type) throws JSONException {
233 | if (obj == null) {
234 | return null;
235 | }
236 | if ((type == ExternalGroup.class || type == ExternalIdentity.class) && obj.has(PN_MEMBERS)) {
237 | if (ref == null) {
238 | ref = new ExternalIdentityRef(id, getName());
239 | }
240 | //noinspection unchecked
241 | return (T) new ExternalGroupImpl(getName(), ref, id, convertJSONtoMap(obj));
242 |
243 | } else if ((type == ExternalUser.class || type == ExternalIdentity.class) && !obj.has(PN_MEMBERS)) {
244 | if (ref == null) {
245 | ref = new ExternalIdentityRef(id, getName());
246 | }
247 | //noinspection unchecked
248 | return (T) new ExternalUserImpl(getName(), ref, id, convertJSONtoMap(obj));
249 | } else {
250 | return null;
251 | }
252 | }
253 |
254 | /**
255 | * Returns an iterator over all identities of the given type
256 | * @param type the type
257 | * @return an iterator
258 | * @throws ExternalIdentityException
259 | */
260 | @Nonnull
261 | private Iterator listIdentities(@Nonnull Class type) throws ExternalIdentityException {
262 | try {
263 | List identities = new ArrayList();
264 | JSONObject obj = loadJSON();
265 | JSONArray names = obj.names();
266 | for (int i=0; i convertJSONtoMap(@Nonnull JSONObject obj) throws JSONException {
288 | Map props = new HashMap();
289 | JSONArray names = obj.names();
290 | for (int i=0; i listUsers() throws ExternalIdentityException {
414 | return listIdentities(ExternalUser.class);
415 | }
416 |
417 | /**
418 | * List all external groups.
419 | * @return an iterator over all external groups
420 | * @throws ExternalIdentityException if an error occurs.
421 | */
422 | @Nonnull
423 | public Iterator listGroups() throws ExternalIdentityException {
424 | return listIdentities(ExternalGroup.class);
425 | }
426 |
427 | }
--------------------------------------------------------------------------------
/step-04/images/example-01-find-json-idp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Adobe-Marketing-Cloud/aem-ldap-tutorial/c19b8f0cb1f828eeb311fa12e22e3c9323457ca2/step-04/images/example-01-find-json-idp.png
--------------------------------------------------------------------------------
/step-04/images/example-02-configure-idp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Adobe-Marketing-Cloud/aem-ldap-tutorial/c19b8f0cb1f828eeb311fa12e22e3c9323457ca2/step-04/images/example-02-configure-idp.png
--------------------------------------------------------------------------------
/step-04/images/example-03-configure-synchandler.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Adobe-Marketing-Cloud/aem-ldap-tutorial/c19b8f0cb1f828eeb311fa12e22e3c9323457ca2/step-04/images/example-03-configure-synchandler.png
--------------------------------------------------------------------------------
/step-04/images/example-04-configure-loginmodule.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Adobe-Marketing-Cloud/aem-ldap-tutorial/c19b8f0cb1f828eeb311fa12e22e3c9323457ca2/step-04/images/example-04-configure-loginmodule.png
--------------------------------------------------------------------------------
/step-04/images/example-05-useradmin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Adobe-Marketing-Cloud/aem-ldap-tutorial/c19b8f0cb1f828eeb311fa12e22e3c9323457ca2/step-04/images/example-05-useradmin.png
--------------------------------------------------------------------------------
/step-04/json-idp-config-pkg.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Adobe-Marketing-Cloud/aem-ldap-tutorial/c19b8f0cb1f828eeb311fa12e22e3c9323457ca2/step-04/json-idp-config-pkg.zip
--------------------------------------------------------------------------------
/step-04/tutorial-04-example-idp.md:
--------------------------------------------------------------------------------
1 | AEM 6.1 Authentication Tutorial Bonus
2 | =====================================
3 |
4 | Step 04 - Creating your own IDP
5 | -------------------------------
6 | In this step we show you how to create your own IDP. The IDP provided in this example is very simple as it reads the authorizable data from a JSON file. The sample data in [authorizables.json](authorizables.json) provides the crews of 2 fictional spaceships.
7 |
8 | The code for the new IDP is located in the [example-idp](example-idp/) directory. The majority of the code is in the [JsonFileIdentityProvider.java](example-idp/src/main/java/com/adobe/gems/exampleidp/impl/JsonFileIdentityProvider.java) class. The code is very simple and should be self explanatory.
9 |
10 | **!** If you want to fast forward, here's the [json-idp-config-pkg.zip](json-idp-config-pkg.zip) package with the configuration. all you need to do is to build and deploy the bundle.
11 |
12 | ### Requirements
13 | 1. Running vanilla AEM 6.1 installation
14 | 2. Apache maven
15 |
16 | #### 1. build the bundle
17 | - open a terminal or command prompt and navigate into the `example-idp` directory.
18 | - build and install the bundle with `mvn clean install sling:install`
19 |
20 | the output should show something like:
21 |
22 | ````
23 | ...
24 | [INFO] ------------------------------------------------------------------------
25 | [INFO] Building AEM 6.1 Gems Example IDP 1.0-SNAPSHOT
26 | [INFO] ------------------------------------------------------------------------
27 | ...
28 | [INFO]
29 | [INFO] --- maven-sling-plugin:2.1.0:install (default-cli) @ com.adobe.gems.exampleidp ---
30 | [INFO] Installing Bundle com.adobe.gems.exampleidp(.../example-idp/target/com.adobe.gems.exampleidp-1.0-SNAPSHOT.jar) to http://localhost:4502/system/console via POST
31 | [INFO] Bundle installed
32 | [INFO] ------------------------------------------------------------------------
33 | [INFO] BUILD SUCCESS
34 | [INFO] ------------------------------------------------------------------------
35 | ...
36 | ````
37 |
38 | This will build the bundle and automatically deploy it into an AEM instance running at localhost:4502.
39 |
40 | #### 2. configure the identity provider
41 | Similar to the steps when configuring the ldap idp, we need to create a configuration for our new IDP.
42 |
43 | - open the [Felix Configuration Manager](http://localhost:4502/system/console/configMgr) and search for the _"json file identity provider"_ factory config and click on the plus **+** button.
44 |
45 | 
46 |
47 | Enter the following information:
48 |
49 |
50 | | Name | Value |
51 | |---------------|--------------------|
52 | | Provider Name | json |
53 | | JSON Filename | authorizables.json |
54 |
55 |
56 | 
57 |
58 | #### 3. copy the authorizables json file
59 | As soon as you configure the IDP, you should see a log entry like this:
60 |
61 | ```
62 | 23.07.2015 14:15:15.936 *INFO* [CM Event Dispatcher (Fire ConfigurationEvent: pid=com.adobe.gems.exampleidp.impl.JsonFileIdentityProvider.467a467e-6559-4e40-bb7e-041becb6819e)] com.adobe.gems.exampleidp.impl.JsonFileIdentityProvider json file IDP initialized. using file: /data/develop/cq5/6.1/author/authorizables.json
63 | ```
64 |
65 | as you can see, the file that the IDP needs is relative to where you started AEM. so copy the sample [authorizables.json](authorizables.json) there.
66 |
67 | #### 4. configure the sync handler
68 | Similar to the steps when configuring the sync handler for the ldap idp, we need to create a new configuration here as well.
69 |
70 | - open the [Felix Configuration Manager](http://localhost:4502/system/console/configMgr) and search for the _"Default Sync Handler"_ factory config and click on the plus **+** button.
71 |
72 | 
73 |
74 | Enter the following information:
75 |
76 | | Name | Value
77 | |-------------------------------|--------------------
78 | | Sync Handler Name | `tutorial_handler`
79 | | User Expiration Time | `10s`
80 | | User auto membership | `contributor`
81 | | User property mapping | `rep:fullname=cn`
`profile/nt:primaryType="nt:unstructured"`
`profile/givenName=givenname`
`profile/familyName=familyname`
`profile/email=email` |
82 | | User Path Prefix | `/seven_skies`
83 | | User Membership Expiration | `1h`
84 | | User membership nesting depth | `1`
85 | | Group Expiration Time | `1d`
86 | | Group auto membership |
87 | | Group property mapping |
88 | | Group Path Prefix | `/seven_skies`
89 |
90 | And save the config. We already add the mappings for the extra profile properties, as we learned in [Step 03](../step-03/tutorial-03-test.md)
91 |
92 | 
93 |
94 | #### 5. configure the external login module
95 | As before, the external login module is the bridge between the login, the idp and the sync handler. so add a new configuration that pairs the new `json` idp with the `tutorial_handler` sync handler
96 |
97 | - open the [Felix Configuration Manager](http://localhost:4502/system/console/configMgr) and search for the _"External Login Module"_ factory config and click on the plus **+** button.
98 |
99 | 
100 |
101 | Enter the following information:
102 |
103 | | Name | Value
104 | |------------------------|----------
105 | | JAAS Ranking | 50
106 | | JAAS Control Flag | `SUFFICIENT`
107 | | JAAS Realm |
108 | | Identity Provider Name | `json`
109 | | Sync Handler Name | `tutorial_handler`
110 |
111 | And save the config
112 |
113 | 
114 |
115 | #### 6. test that it works
116 | - ensure to logout previous session or clear all browser cookie or use a different browser, hostname or IP
117 | - open browser to aem: http://127.0.0.1:4502/
118 | - login as `kirk` with password `pass`
119 |
120 | If the login succeeds, you should now see the authoring environment because we configured the `user.autoMembership` to include the `contributor` group.
121 |
122 | Looking at the users and groups should show the group _enterprise_ and his member(s):
123 |
124 | - open the [AEM useradmin](http://localhost:4502/useradmin)
125 | - search for `enterprise`
126 | - after that, use the JMX console to import the test of the space team!
127 |
128 | 
129 |
130 |
131 |
--------------------------------------------------------------------------------