Skip to content

Commit 9fb4fe2

Browse files
committed
Delegated authentication
1 parent f69fba7 commit 9fb4fe2

File tree

10 files changed

+538
-0
lines changed

10 files changed

+538
-0
lines changed

external-authn/README.md

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
# External Authn
2+
3+
## Design
4+
```
5+
Title External Authn
6+
7+
actor Person
8+
participant Browser
9+
participant Website1
10+
participant Website2
11+
participant IDP
12+
database cache
13+
14+
Person->Browser: 1. Navigate to website1
15+
Browser->Website1:
16+
Website1->Browser: 2. redirect
17+
group Person Authn Script : Step One
18+
Browser->IDP: 3. [GET] /oxauth/authorize?client_id=__&redirect_uri=__&state=__\n&nonce=__&prompt=none&scope=__\n&response_mode=__&response_type=__
19+
IDP->IDP: 4. generate jans_key
20+
IDP<->cache: cache in session context\njans_key: {request params}
21+
IDP->Browser: 5. redirect /internal.idp?________\nSet Pre-Authn Session Cookie
22+
Browser->Website2:
23+
end
24+
Website2->Browser: 6. Display login page
25+
Person->Browser: 7. Enter Username / PW
26+
Browser->Website2: 8. (creds)
27+
group ROPW script
28+
Website2->IDP: 9. /oxauth/token?uid=__&pw="__&browser_ip=__&jans_key=__
29+
IDP->IDP: 10. update cache:\n "jans_key": "auth_success"
30+
IDP->IDP: 11. retreive user claims
31+
IDP->Website2:12. {\n "callback_url":"https://op-host/oxauth**/authorize?jansKey={jansKey}&redirect_uri={original_redirect}&...**",\n "userinfo": {"uid": "__",...}\n }
32+
end
33+
group Person Authn Script Step 2
34+
Website2->Browser: 13. write website 2 cookie;\n302 Location IDP callback_url
35+
Browser->IDP: 14. callback_url_from_step_12
36+
IDP->IDP: 15. get session context
37+
IDP->cache:16. delete jans_key\n lookup original redirect_uri
38+
IDP->Browser: 17. write IDP session cookie\nand 302: Location original redirect_uri
39+
end
40+
Browser->Website1:
41+
Website1->Website1: optional: 18 Validate id_token\n (claims optional)
42+
```
43+
![](assets/external-authn-diagram.png)
44+
45+
Follow the instructions below to set up:
46+
47+
## OxAuth Configuration
48+
Enable **openidScopeBackwardCompatibility**
49+
![openidScopeBackwardCompatibility](assets/openidscope-bc.png)
50+
51+
Add new custom param **jansKey**
52+
![custom param jansKey](assets/janskey-custom-param.png)
53+
54+
## Enable Custom Script
55+
56+
- ### Person Authentication - External Authn
57+
58+
Create a new record in table **oxCustomScript**.
59+
```
60+
INSERT INTO oxCustomScript ( doc_id, objectClass, dn, displayName, oxEnabled, oxRevision, oxScript, oxAlias, oxScriptType, oxModuleProperty, programmingLanguage, oxScriptError, oxConfigurationProperty, inum, description, oxLevel )
61+
VALUES ( 'PA01-EA01', 'oxCustomScript', 'inum=PA01-EA01,ou=scripts,o=gluu', 'pa-external-authn', 0, 1, '', '{"v": []}', 'person_authentication', '{"v": ["{\\"value1\\":\\"usage_type\\",\\"value2\\":\\"interactive\\",\\"description\\":\\"\\"}", "{\\"value1\\":\\"location_type\\",\\"value2\\":\\"ldap\\",\\"description\\":\\"\\"}"]}', 'python', NULL, '{"v": ["{\\"value1\\":\\"urlstep1\\",\\"value2\\":\\"http://demoexample.net:81\\",\\"hide\\":false,\\"description\\":\\"Url to return in step 1\\"}"]}', 'PA01-EA01', 'PA External Authn', 10 );
62+
```
63+
64+
Modify the **oxConfigurationProperty** field by replacing **URL_REDIRECT_URI** with the url that you want to return to the first step
65+
```
66+
'{"v": ["{\\"value1\\":\\"urlstep1\\",\\"value2\\":\\"{URL_REDIRECT_URI}\\",\\"hide\\":false,\\"description\\":\\"Url to return in step 1\\"}"]}'
67+
```
68+
![](assets/pa-property.png)
69+
70+
Modify the **oxScript** field by adding the content of the following link: [PersonAuthentication Script](pyscript/pa-external-authn.py)
71+
72+
- ### ROPC (Resource Owner Password Credentials) Script - External Authn
73+
74+
Create a new record in table **oxCustomScript**.
75+
```
76+
INSERT INTO oxCustomScript ( doc_id, objectClass, dn, displayName, oxEnabled, oxRevision, oxScript, oxAlias, oxScriptType, oxModuleProperty, programmingLanguage, oxScriptError, oxConfigurationProperty, inum, description, oxLevel )
77+
VALUES ( 'ROPC-EA01', 'oxCustomScript', 'inum=ROPC-EA01,ou=scripts,o=gluu', 'ropc-external-authn', 0, 1, '', '{"v": []}', 'resource_owner_password_credentials', '{"v": ["{\\"value1\\":\\"location_type\\",\\"value2\\":\\"ldap\\",\\"description\\":\\"\\"}"]}', 'python', NULL, '{"v": []}', 'ROPC-EA01', 'ROPC External Authn', 1 );
78+
```
79+
80+
Modify the **oxScript** field by adding the content of the following link: [ROPC (Resource Owner Password Credentials) Script](pyscript/ropc-external-authn.py)
81+
82+
- ### Update Token Script - External Authn
83+
84+
Create a new record in table **oxCustomScript**.
85+
```
86+
INSERT INTO oxCustomScript ( doc_id, objectClass, dn, displayName, oxEnabled, oxRevision, oxScript, oxAlias, oxScriptType, oxModuleProperty, programmingLanguage, oxScriptError, oxConfigurationProperty, inum, description, oxLevel )
87+
VALUES ( 'UPDT-EA01', 'oxCustomScript', 'inum=UPDT-EA01,ou=scripts,o=gluu', 'update-token-external-authn', 0, 1, '', '{"v": []}', 'update_token', '{"v": ["{\\"value1\\":\\"location_type\\",\\"value2\\":\\"ldap\\",\\"description\\":\\"\\"}"]}', 'python', NULL, '{"v": []}', 'UPDT-EA01', 'Update token External Authn', 1 );
88+
```
89+
90+
Modify the **oxScript** field by adding the content of the following link: [Update Token Script](pyscript/ut-external-authn.py)
91+
92+
In this script you can choose whether to use the header or payload of the **id_token** for the **callback_url**:
93+
```
94+
jsonWebResponse.getHeader().setClaim("callback_url", jsonValCallbackUrl)
95+
jsonWebResponse.getClaims().setClaim("callback_url", jsonValCallbackUrl)
96+
```
97+
98+
## Client Configuration
99+
Enable acr default values, add the previously configured custom script
100+
![](assets/client-config-external-aux.png)
101+
102+
Enable **PreAuthorization**
103+
![PreAuthorization](assets/client-preauthz.png)
104+
105+
## Call Flow
106+
- ### Step 1: /authorize
107+
Request:
108+
```
109+
curl --location --request GET 'https://{your-gluu-url}/oxauth/restv1/authorize?response_type=code&client_id=14e36e18-1d51-41ac-a4cf-a7dc677f53a5&scope=openid+profile+address+email&redirect_uri=https://jans.localhost/jans-auth-rp/home.htm&state=a84dd61f-533c-46a4-9315-a66fda3e9a4e&nonce=80e6bd2b-eb78-48b9-be9c-6bb33ef80991&ui_locales=&claims_locales=&request_session_id=false&acr_values='
110+
```
111+
Response: (return the **redirect_uri** with jansKey)
112+
```
113+
http://demoexample.net:81?jansKey=46340f40-a554-46b1-9246-37c2e869919f
114+
```
115+
116+
- ### Step 2: /token
117+
Request: (**Authorization** = Basic base64(client_id:client_secret))
118+
```
119+
curl --location --request POST 'https://{your-gluu-url}/oxauth/restv1/token' \
120+
--header 'Authorization: Basic MTRlMzZlMTgtMWQ1MS00MWFjLWE0Y2YtYTdkYzY3N2Y1M2E1Ojk5NzE4NWU1LTc2NGUtNGE4Yi1hNjYwLTdjZmQ4NzJhNjc0Ng==' \
121+
--header 'Content-Type: application/x-www-form-urlencoded' \
122+
--data-urlencode 'grant_type=password' \
123+
--data-urlencode 'username=test_user' \
124+
--data-urlencode 'password=test_user_password' \
125+
--data-urlencode 'scope=openid' \
126+
--data-urlencode 'jansKey=46340f40-a554-46b1-9246-37c2e869919f'
127+
```
128+
Response: (id_token contains in header or payload callback_url)
129+
```
130+
{
131+
"access_token": "a0878887-b998-4da4-aa0b-4e74bd9a4441",
132+
"refresh_token": "d8b618ac-9d9c-4b90-9cac-aafb1e38e82e",
133+
"scope": "openid",
134+
"id_token": "eyJjYWxsYmFja191cmwiOiJodHRwczovL2RlbW9leGFtcGxlLmdsdXUub3JnL294YXV0aC9yZXN0djEvYXV0aG9yaXplP3Jlc3BvbnNlX3R5cGU9Y29kZSZyZWRpcmVjdF91cmk9aHR0cHMlM0ElMkYlMkZkZW1vZXhhbXBsZS5nbHV1Lm9yZyUyRm94YXV0aC1ycCUyRmhvbWUuaHRtJmphbnNLZXk9MGZiZmU2ZmUtY2YzZi00NGU3LWE0MzMtNjE3OWMzNTk4OTAzJmNsaWVudF9pZD1jZjlhOGExMC00MWJlLTQ0OTEtODdlNC1mY2MxMDlmOGRhMmMiLCJraWQiOiJmOTllNzVmMy1jYWQ4LTRmMzgtYTdlYi05Njc0ZWRjYTA5NGRfc2lnX3JzMjU2IiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.eyJjYWxsYmFja191cmwiOiJodHRwczovL2RlbW9leGFtcGxlLmdsdXUub3JnL294YXV0aC9yZXN0djEvYXV0aG9yaXplP3Jlc3BvbnNlX3R5cGU9Y29kZSZyZWRpcmVjdF91cmk9aHR0cHMlM0ElMkYlMkZkZW1vZXhhbXBsZS5nbHV1Lm9yZyUyRm94YXV0aC1ycCUyRmhvbWUuaHRtJmphbnNLZXk9MGZiZmU2ZmUtY2YzZi00NGU3LWE0MzMtNjE3OWMzNTk4OTAzJmNsaWVudF9pZD1jZjlhOGExMC00MWJlLTQ0OTEtODdlNC1mY2MxMDlmOGRhMmMiLCJhdWQiOiJjZjlhOGExMC00MWJlLTQ0OTEtODdlNC1mY2MxMDlmOGRhMmMiLCJhY3IiOiJzaW1wbGVfcGFzc3dvcmRfYXV0aCIsInN1YiI6IjAzNEp3b1dtNEgxUFVyLThrMEdTQzI2NWxPS2s4Z3ljTHN5MDVlbUhjNEUiLCJjb2RlIjoiZTYxYzViYmEtNTk1OC00ODk3LTg4MTItNmU5MDAwMDE1NWRmIiwiYW1yIjpbIi0xIl0sImlzcyI6Imh0dHBzOi8vZGVtb2V4YW1wbGUuZ2x1dS5vcmciLCJleHAiOjE2NjEzNDYyNDMsImdyYW50IjoicGFzc3dvcmQiLCJpYXQiOjE2NjEzNDI2NDMsInNpZCI6ImRhOTIxZWI2LTQ1MGItNGJlMC05ODI4LTM4ZWQ2NTcyNmVjYiIsIm94T3BlbklEQ29ubmVjdFZlcnNpb24iOiJvcGVuaWRjb25uZWN0LTEuMCJ9.mffKKvsGWV1qmUy98B7H9RCR-4usP8jOsGEif419prR2cN9fWRSSFC7WTJr6Myh5EEJDAb_tQnA9TSTtP5XlTP41B9l02RMvJINCMYnBlUbP5L6WzowH4N3j7CH6V96ruM_w-dAoeqgAmbMCQCG1b5BQQjZntE16GQOvUnA6IukbKBv5vzPQn74cxhIKYL6b6BePT4oiQ2WOdbQqGEBFPHebmzLzGoIK60YgSr3qToZHt3WUP0TbTEpcvQb9oAaanCdjdP12Y6gZOqQK452GmygCZxZ_8wnFENAWF4rj85kdCFu5ucM40n-K7RpAclPPCGU_hJTKGr0BfOGIPOP3OA",
135+
"token_type": "Bearer",
136+
"expires_in": 299
137+
}
138+
```
139+
140+
- ### Step 3: callback_uri (/authorize)
141+
142+
Request:
143+
```
144+
curl --location --request GET 'https://{your-gluu-url}/oxauth/restv1/authorize?response_type=code&redirect_uri=https%3A%2F%2Fjans.localhost%2Fjans-auth-rp%2Fhome.htm&client_id=14e36e18-1d51-41ac-a4cf-a7dc677f53a5&jansKey=46340f40-a554-46b1-9246-37c2e869919f'
145+
```
146+
147+
Response: (return to the **redirect_uri**)
148+
```
149+
https://jans.localhost/jans-auth-rp/home.htm?code=441688df-8f36-4e2c-8174-18d23cc88049&acr_values=pa-external-authn&session_id=7ee59d72-d59a-49ce-a0cb-19c4fcfc404c&session_state=c3f595a892208e3d237722ad06d830f199295ccc355827c436fff71509401eae.a505421b-a332-4604-8772-6ca345c4a4b9
150+
```
20.9 KB
Loading
19.8 KB
Loading
213 KB
Loading
49 KB
Loading
21.2 KB
Loading
19.6 KB
Loading
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
# PersonAuthentication External Authn
2+
3+
from org.gluu.model.custom.script.type.auth import PersonAuthenticationType
4+
from org.gluu.service.cdi.util import CdiUtil
5+
from org.gluu.oxauth.security import Identity
6+
from org.gluu.oxauth.service import AuthenticationService
7+
from org.gluu.util import StringHelper
8+
from org.gluu.oxauth.util import ServerUtil
9+
from org.gluu.oxauth.service import SessionIdService
10+
from org.gluu.oxauth.service import CookieService
11+
from org.gluu.service.cache import CacheProvider
12+
from javax.faces.context import ExternalContext
13+
from java.util import HashMap
14+
from org.gluu.oxauth.service import UserService, RequestParameterService
15+
from org.gluu.oxauth.service.net import HttpService
16+
from javax.faces.context import FacesContext
17+
from org.gluu.jsf2.service import FacesService
18+
19+
import java
20+
import uuid
21+
22+
class PersonAuthentication(PersonAuthenticationType):
23+
def __init__(self, currentTimeMillis):
24+
self.currentTimeMillis = currentTimeMillis
25+
26+
def init(self, customScript, configurationAttributes):
27+
print "PA External Authn. Initialization"
28+
print "PA External Authn. Initialized successfully configurationAttributes = %s" % configurationAttributes
29+
30+
self.url_step1 = None
31+
32+
# Get Custom Properties
33+
try:
34+
self.url_step1 = configurationAttributes.get("urlstep1").getValue2()
35+
print "PA External Authn. Initialization. url_step1: '%s'" % self.url_step1
36+
except:
37+
print 'Missing required configuration attribute "urlstep1"'
38+
39+
return True
40+
41+
def destroy(self, configurationAttributes):
42+
print "PA External Authn. Destroy"
43+
print "PA External Authn. Destroyed successfully"
44+
return True
45+
46+
def getAuthenticationMethodClaims(self, requestParameters):
47+
return None
48+
49+
def getApiVersion(self):
50+
return 11
51+
52+
def isValidAuthenticationMethod(self, usageType, configurationAttributes):
53+
return True
54+
55+
def getAlternativeAuthenticationMethod(self, usageType, configurationAttributes):
56+
return None
57+
58+
def authenticate(self, configurationAttributes, requestParameters, step):
59+
print "PA External Authn. Authenticate, step: %s, requestParameters: %s" % (step, requestParameters)
60+
61+
# Retrieve jansKey from request params
62+
jansKey = ServerUtil.getFirstValue(requestParameters, "jansKey")
63+
if (jansKey == None):
64+
print "PA External Authn. Authenticate. Could not find jansKey in request param"
65+
return False
66+
print "PA External Authn. Authenticate. jansKey found in request param: '%s'" % jansKey
67+
68+
# Retrieve jsonValues from cache
69+
cacheProvider = CdiUtil.bean(CacheProvider)
70+
jsonValues = cacheProvider.get(jansKey)
71+
if (jsonValues == None):
72+
print "PA External Authn. Authenticate. Could not find jsonValues in cache"
73+
return False
74+
print "PA External Authn. Authenticate. jsonValues found in cache: %s" % jsonValues
75+
76+
# Retrieve sessionDn from cacheProvider
77+
sessionDn = jsonValues.get("sessionDn")
78+
if (sessionDn == None):
79+
print "PA External Authn. Authenticate. Could not find sessionDn in cache"
80+
return False
81+
print "PA External Authn. Authenticate. sessionDn found in cache: '%s'" % sessionDn
82+
83+
# Retrieve sessionId by dn
84+
sessionId = CdiUtil.bean(SessionIdService).getSessionByDn(sessionDn)
85+
if (sessionId == None):
86+
print "PA External Authn. Authenticate. Could not find sessionId by dn: '%s'" % sessionDn
87+
return False
88+
print "PA External Authn. Authenticate. sessionId found by dn: '%s'" % sessionId.getId()
89+
90+
# Write sessionId in cookies
91+
cookieService = CdiUtil.bean(CookieService)
92+
cookieService.createSessionIdCookie(sessionId, False)
93+
print "PA External Authn. Authenticate. Writed session in cookies"
94+
95+
# Set sessionId in Identity
96+
identity = CdiUtil.bean(Identity)
97+
identity.setSessionId(sessionId)
98+
print "PA External Authn. Authenticate. Setted session in identity"
99+
100+
# Remove jansKey from cache
101+
cacheProvider.remove(jansKey)
102+
print "PA External Authn. Authenticate. jansKey removed from cache"
103+
104+
return True
105+
106+
def prepareForStep(self, configurationAttributes, requestParameters, step):
107+
if (step == 1):
108+
return True
109+
else:
110+
return False
111+
112+
def getExtraParametersForStep(self, configurationAttributes, step):
113+
return None
114+
115+
def getCountAuthenticationSteps(self, configurationAttributes):
116+
return 1
117+
118+
def getPageForStep(self, configurationAttributes, step):
119+
print "PA External Authn. GetPageForStep step: %s" % step
120+
121+
externalContext = CdiUtil.bean(ExternalContext)
122+
jansKeyParam = ServerUtil.getFirstValue(externalContext.getRequestParameterValuesMap(), "jansKey")
123+
if (jansKeyParam == None):
124+
print "PA External Authn. GetPageForStep could not found jansKey in request param"
125+
126+
# Retrieve redirectUri from request param and validate it
127+
redirectUri = ServerUtil.getFirstValue(externalContext.getRequestParameterValuesMap(), "redirect_uri")
128+
if (redirectUri == None or StringHelper.isEmpty(redirectUri)):
129+
print "PA External Authn. GetPageForStep redirect_uri is null or empty"
130+
return ""
131+
print "PA External Authn. GetPageForStep redirect_uri '%s' found in request param" % redirectUri
132+
133+
clientId = ServerUtil.getFirstValue(externalContext.getRequestParameterValuesMap(), "client_id")
134+
if (clientId == None or StringHelper.isEmpty(clientId)):
135+
print "PA External Authn. GetPageForStep client_id is null or empty"
136+
return ""
137+
print "PA External Authn. GetPageForStep client_id '%s' found in request param" % clientId
138+
139+
# Generate jansKey
140+
jansKey = str(uuid.uuid4());
141+
print "PA External Authn. GetPageForStep jansKey '%s' generated" % jansKey
142+
143+
# Create JSON Values
144+
jsonValues = {}
145+
jsonValues["redirectUri"] = str(redirectUri)
146+
jsonValues["clientId"] = str(clientId)
147+
148+
cacheProvider = CdiUtil.bean(CacheProvider)
149+
cacheProvider.put(300, jansKey, jsonValues)
150+
print "PA External Authn. GetPageForStep jansKey '%s' added to cache: %s" % (jansKey, jsonValues)
151+
152+
requestParameterService = CdiUtil.bean(RequestParameterService)
153+
parametersMap = HashMap()
154+
parametersMap.put("jansKey", jansKey)
155+
callBackUrl = requestParameterService.parametersAsString(parametersMap)
156+
callBackUrl = "%s?%s" % (self.url_step1, callBackUrl)
157+
158+
print "PA External Authn. GetPageForStep redirect to %s" % callBackUrl
159+
160+
facesService = CdiUtil.bean(FacesService)
161+
facesService.redirectToExternalURL(callBackUrl)
162+
163+
return ""
164+
165+
print "PA External Authn. GetPageForStep jansKey found in request param: %s" % jansKeyParam
166+
return "postlogin.xhtml"
167+
168+
def getNextStep(self, configurationAttributes, requestParameters, step):
169+
return -1
170+
171+
def getLogoutExternalUrl(self, configurationAttributes, requestParameters):
172+
return None
173+
174+
def logout(self, configurationAttributes, requestParameters):
175+
return True

0 commit comments

Comments
 (0)