Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DRAFT] External Authentication #1711

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 150 additions & 0 deletions external-authn/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# External Authn

## Design
```
Title External Authn

actor Person
participant Browser
participant Website1
participant Website2
participant IDP
database cache

Person->Browser: 1. Navigate to website1
Browser->Website1:
Website1->Browser: 2. redirect
group Person Authn Script : Step One
Browser->IDP: 3. [GET] /oxauth/authorize?client_id=__&redirect_uri=__&state=__\n&nonce=__&prompt=none&scope=__\n&response_mode=__&response_type=__
IDP->IDP: 4. generate jans_key
IDP<->cache: cache in session context\njans_key: {request params}
IDP->Browser: 5. redirect /internal.idp?________\nSet Pre-Authn Session Cookie
Browser->Website2:
end
Website2->Browser: 6. Display login page
Person->Browser: 7. Enter Username / PW
Browser->Website2: 8. (creds)
group ROPW script
Website2->IDP: 9. /oxauth/token?uid=__&pw="__&browser_ip=__&jans_key=__
IDP->IDP: 10. update cache:\n "jans_key": "auth_success"
IDP->IDP: 11. retreive user claims
IDP->Website2:12. {\n "callback_url":"https://op-host/oxauth**/authorize?jansKey={jansKey}&redirect_uri={original_redirect}&...**",\n "userinfo": {"uid": "__",...}\n }
end
group Person Authn Script Step 2
Website2->Browser: 13. write website 2 cookie;\n302 Location IDP callback_url
Browser->IDP: 14. callback_url_from_step_12
IDP->IDP: 15. get session context
IDP->cache:16. delete jans_key\n lookup original redirect_uri
IDP->Browser: 17. write IDP session cookie\nand 302: Location original redirect_uri
end
Browser->Website1:
Website1->Website1: optional: 18 Validate id_token\n (claims optional)
```
![](assets/external-authn-diagram.png)

Follow the instructions below to set up:

## OxAuth Configuration
Enable **openidScopeBackwardCompatibility**
![openidScopeBackwardCompatibility](assets/openidscope-bc.png)

Add new custom param **jansKey**
![custom param jansKey](assets/janskey-custom-param.png)

## Enable Custom Script

- ### Person Authentication - External Authn

Create a new record in table **oxCustomScript**.
```
INSERT INTO oxCustomScript ( doc_id, objectClass, dn, displayName, oxEnabled, oxRevision, oxScript, oxAlias, oxScriptType, oxModuleProperty, programmingLanguage, oxScriptError, oxConfigurationProperty, inum, description, oxLevel )
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 );
```

Modify the **oxConfigurationProperty** field by replacing **URL_REDIRECT_URI** with the url that you want to return to the first step
```
'{"v": ["{\\"value1\\":\\"urlstep1\\",\\"value2\\":\\"{URL_REDIRECT_URI}\\",\\"hide\\":false,\\"description\\":\\"Url to return in step 1\\"}"]}'
```
![](assets/pa-property.png)

Modify the **oxScript** field by adding the content of the following link: [PersonAuthentication Script](pyscript/pa-external-authn.py)

- ### ROPC (Resource Owner Password Credentials) Script - External Authn

Create a new record in table **oxCustomScript**.
```
INSERT INTO oxCustomScript ( doc_id, objectClass, dn, displayName, oxEnabled, oxRevision, oxScript, oxAlias, oxScriptType, oxModuleProperty, programmingLanguage, oxScriptError, oxConfigurationProperty, inum, description, oxLevel )
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 );
```

Modify the **oxScript** field by adding the content of the following link: [ROPC (Resource Owner Password Credentials) Script](pyscript/ropc-external-authn.py)

- ### Update Token Script - External Authn

Create a new record in table **oxCustomScript**.
```
INSERT INTO oxCustomScript ( doc_id, objectClass, dn, displayName, oxEnabled, oxRevision, oxScript, oxAlias, oxScriptType, oxModuleProperty, programmingLanguage, oxScriptError, oxConfigurationProperty, inum, description, oxLevel )
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 );
```

Modify the **oxScript** field by adding the content of the following link: [Update Token Script](pyscript/ut-external-authn.py)

In this script you can choose whether to use the header or payload of the **id_token** for the **callback_url**:
```
jsonWebResponse.getHeader().setClaim("callback_url", jsonValCallbackUrl)
jsonWebResponse.getClaims().setClaim("callback_url", jsonValCallbackUrl)
```

## Client Configuration
Enable acr default values, add the previously configured custom script
![](assets/client-config-external-aux.png)

Enable **PreAuthorization**
![PreAuthorization](assets/client-preauthz.png)

## Call Flow
- ### Step 1: /authorize
Request:
```
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='
```
Response: (return the **redirect_uri** with jansKey)
```
http://demoexample.net:81?jansKey=46340f40-a554-46b1-9246-37c2e869919f
```

- ### Step 2: /token
Request: (**Authorization** = Basic base64(client_id:client_secret))
```
curl --location --request POST 'https://{your-gluu-url}/oxauth/restv1/token' \
--header 'Authorization: Basic MTRlMzZlMTgtMWQ1MS00MWFjLWE0Y2YtYTdkYzY3N2Y1M2E1Ojk5NzE4NWU1LTc2NGUtNGE4Yi1hNjYwLTdjZmQ4NzJhNjc0Ng==' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=password' \
--data-urlencode 'username=test_user' \
--data-urlencode 'password=test_user_password' \
--data-urlencode 'scope=openid' \
--data-urlencode 'jansKey=46340f40-a554-46b1-9246-37c2e869919f'
```
Response: (id_token contains in header or payload callback_url)
```
{
"access_token": "a0878887-b998-4da4-aa0b-4e74bd9a4441",
"refresh_token": "d8b618ac-9d9c-4b90-9cac-aafb1e38e82e",
"scope": "openid",
"id_token": "eyJjYWxsYmFja191cmwiOiJodHRwczovL2RlbW9leGFtcGxlLmdsdXUub3JnL294YXV0aC9yZXN0djEvYXV0aG9yaXplP3Jlc3BvbnNlX3R5cGU9Y29kZSZyZWRpcmVjdF91cmk9aHR0cHMlM0ElMkYlMkZkZW1vZXhhbXBsZS5nbHV1Lm9yZyUyRm94YXV0aC1ycCUyRmhvbWUuaHRtJmphbnNLZXk9MGZiZmU2ZmUtY2YzZi00NGU3LWE0MzMtNjE3OWMzNTk4OTAzJmNsaWVudF9pZD1jZjlhOGExMC00MWJlLTQ0OTEtODdlNC1mY2MxMDlmOGRhMmMiLCJraWQiOiJmOTllNzVmMy1jYWQ4LTRmMzgtYTdlYi05Njc0ZWRjYTA5NGRfc2lnX3JzMjU2IiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.eyJjYWxsYmFja191cmwiOiJodHRwczovL2RlbW9leGFtcGxlLmdsdXUub3JnL294YXV0aC9yZXN0djEvYXV0aG9yaXplP3Jlc3BvbnNlX3R5cGU9Y29kZSZyZWRpcmVjdF91cmk9aHR0cHMlM0ElMkYlMkZkZW1vZXhhbXBsZS5nbHV1Lm9yZyUyRm94YXV0aC1ycCUyRmhvbWUuaHRtJmphbnNLZXk9MGZiZmU2ZmUtY2YzZi00NGU3LWE0MzMtNjE3OWMzNTk4OTAzJmNsaWVudF9pZD1jZjlhOGExMC00MWJlLTQ0OTEtODdlNC1mY2MxMDlmOGRhMmMiLCJhdWQiOiJjZjlhOGExMC00MWJlLTQ0OTEtODdlNC1mY2MxMDlmOGRhMmMiLCJhY3IiOiJzaW1wbGVfcGFzc3dvcmRfYXV0aCIsInN1YiI6IjAzNEp3b1dtNEgxUFVyLThrMEdTQzI2NWxPS2s4Z3ljTHN5MDVlbUhjNEUiLCJjb2RlIjoiZTYxYzViYmEtNTk1OC00ODk3LTg4MTItNmU5MDAwMDE1NWRmIiwiYW1yIjpbIi0xIl0sImlzcyI6Imh0dHBzOi8vZGVtb2V4YW1wbGUuZ2x1dS5vcmciLCJleHAiOjE2NjEzNDYyNDMsImdyYW50IjoicGFzc3dvcmQiLCJpYXQiOjE2NjEzNDI2NDMsInNpZCI6ImRhOTIxZWI2LTQ1MGItNGJlMC05ODI4LTM4ZWQ2NTcyNmVjYiIsIm94T3BlbklEQ29ubmVjdFZlcnNpb24iOiJvcGVuaWRjb25uZWN0LTEuMCJ9.mffKKvsGWV1qmUy98B7H9RCR-4usP8jOsGEif419prR2cN9fWRSSFC7WTJr6Myh5EEJDAb_tQnA9TSTtP5XlTP41B9l02RMvJINCMYnBlUbP5L6WzowH4N3j7CH6V96ruM_w-dAoeqgAmbMCQCG1b5BQQjZntE16GQOvUnA6IukbKBv5vzPQn74cxhIKYL6b6BePT4oiQ2WOdbQqGEBFPHebmzLzGoIK60YgSr3qToZHt3WUP0TbTEpcvQb9oAaanCdjdP12Y6gZOqQK452GmygCZxZ_8wnFENAWF4rj85kdCFu5ucM40n-K7RpAclPPCGU_hJTKGr0BfOGIPOP3OA",
"token_type": "Bearer",
"expires_in": 299
}
```

- ### Step 3: callback_uri (/authorize)

Request:
```
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'
```

Response: (return to the **redirect_uri**)
```
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
```
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added external-authn/assets/client-preauthz.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added external-authn/assets/openidscope-bc.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added external-authn/assets/pa-property.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
180 changes: 180 additions & 0 deletions external-authn/pyscript/pa-external-authn.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
# PersonAuthentication External Authn

from org.gluu.model.custom.script.type.auth import PersonAuthenticationType
from org.gluu.service.cdi.util import CdiUtil
from org.gluu.oxauth.security import Identity
from org.gluu.oxauth.service import AuthenticationService
from org.gluu.util import StringHelper
from org.gluu.oxauth.util import ServerUtil
from org.gluu.oxauth.service import SessionIdService
from org.gluu.oxauth.service import CookieService
from org.gluu.service.cache import CacheProvider
from javax.faces.context import ExternalContext
from java.util import HashMap
from org.gluu.oxauth.service import UserService, RequestParameterService
from org.gluu.oxauth.service.net import HttpService
from javax.faces.context import FacesContext
from org.gluu.jsf2.service import FacesService

import java
import uuid

class PersonAuthentication(PersonAuthenticationType):
def __init__(self, currentTimeMillis):
self.currentTimeMillis = currentTimeMillis

def init(self, customScript, configurationAttributes):
print "PA External Authn. Initialization"
print "PA External Authn. Initialized successfully configurationAttributes = %s" % configurationAttributes

self.url_step1 = None

# Get Custom Properties
try:
self.url_step1 = configurationAttributes.get("urlstep1").getValue2()
print "PA External Authn. Initialization. url_step1: '%s'" % self.url_step1
except:
print 'Missing required configuration attribute "urlstep1"'

return True

def destroy(self, configurationAttributes):
print "PA External Authn. Destroy"
print "PA External Authn. Destroyed successfully"
return True

def getAuthenticationMethodClaims(self, requestParameters):
return None

def getApiVersion(self):
return 11

def isValidAuthenticationMethod(self, usageType, configurationAttributes):
return True

def getAlternativeAuthenticationMethod(self, usageType, configurationAttributes):
return None

def authenticate(self, configurationAttributes, requestParameters, step):
print "PA External Authn. Authenticate, step: %s, requestParameters: %s" % (step, requestParameters)

# Retrieve jansKey from request params
jansKey = ServerUtil.getFirstValue(requestParameters, "jansKey")
if (jansKey == None):
print "PA External Authn. Authenticate. Could not find jansKey in request param"
return False
print "PA External Authn. Authenticate. jansKey found in request param: '%s'" % jansKey

# Retrieve jsonValues from cache
cacheProvider = CdiUtil.bean(CacheProvider)
jsonValues = cacheProvider.get(jansKey)
if (jsonValues == None):
print "PA External Authn. Authenticate. Could not find jsonValues in cache"
return False
print "PA External Authn. Authenticate. jsonValues found in cache: %s" % jsonValues

# Retrieve sessionDn from cacheProvider
sessionDn = jsonValues.get("sessionDn")
if (sessionDn == None):
print "PA External Authn. Authenticate. Could not find sessionDn in cache"
return False
print "PA External Authn. Authenticate. sessionDn found in cache: '%s'" % sessionDn

# Retrieve sessionId by dn
sessionId = CdiUtil.bean(SessionIdService).getSessionByDn(sessionDn)
if (sessionId == None):
print "PA External Authn. Authenticate. Could not find sessionId by dn: '%s'" % sessionDn
return False
print "PA External Authn. Authenticate. sessionId found by dn: '%s'" % sessionId.getId()

# Write sessionId in cookies
cookieService = CdiUtil.bean(CookieService)
cookieService.createSessionIdCookie(sessionId, False)
print "PA External Authn. Authenticate. Writed session in cookies"

# Set sessionId in Identity
identity = CdiUtil.bean(Identity)
identity.setSessionId(sessionId)
print "PA External Authn. Authenticate. Setted session in identity"

# Remove jansKey from cache
cacheProvider.remove(jansKey)
print "PA External Authn. Authenticate. jansKey removed from cache"

return True

def prepareForStep(self, configurationAttributes, requestParameters, step):
if (step == 1):
return True
else:
return False

def getExtraParametersForStep(self, configurationAttributes, step):
return None

def getCountAuthenticationSteps(self, configurationAttributes):
return 1

def getPageForStep(self, configurationAttributes, step):
print "PA External Authn. GetPageForStep step: %s" % step

externalContext = CdiUtil.bean(ExternalContext)
jansKeyParam = ServerUtil.getFirstValue(externalContext.getRequestParameterValuesMap(), "jansKey")
if (jansKeyParam == None):
print "PA External Authn. GetPageForStep could not found jansKey in request param"

# Remove session id cookie
cookieService = CdiUtil.bean(CookieService)
cookieService.removeSessionIdCookie(externalContext.getResponse())
print "PA External Authn. GetPageForStep remove session id cookie"

# Retrieve redirectUri from request param and validate it
redirectUri = ServerUtil.getFirstValue(externalContext.getRequestParameterValuesMap(), "redirect_uri")
if (redirectUri == None or StringHelper.isEmpty(redirectUri)):
print "PA External Authn. GetPageForStep redirect_uri is null or empty"
return ""
print "PA External Authn. GetPageForStep redirect_uri '%s' found in request param" % redirectUri

clientId = ServerUtil.getFirstValue(externalContext.getRequestParameterValuesMap(), "client_id")
if (clientId == None or StringHelper.isEmpty(clientId)):
print "PA External Authn. GetPageForStep client_id is null or empty"
return ""
print "PA External Authn. GetPageForStep client_id '%s' found in request param" % clientId

# Generate jansKey
jansKey = str(uuid.uuid4())
print "PA External Authn. GetPageForStep jansKey '%s' generated" % jansKey

# Create JSON Values
jsonValues = {}
jsonValues["redirectUri"] = str(redirectUri)
jsonValues["clientId"] = str(clientId)

cacheProvider = CdiUtil.bean(CacheProvider)
cacheProvider.put(300, jansKey, jsonValues)
print "PA External Authn. GetPageForStep jansKey '%s' added to cache: %s" % (jansKey, jsonValues)

requestParameterService = CdiUtil.bean(RequestParameterService)
parametersMap = HashMap()
parametersMap.put("jansKey", jansKey)
callBackUrl = requestParameterService.parametersAsString(parametersMap)
callBackUrl = "%s?%s" % (self.url_step1, callBackUrl)

print "PA External Authn. GetPageForStep redirect to %s" % callBackUrl

facesService = CdiUtil.bean(FacesService)
facesService.redirectToExternalURL(callBackUrl)

return ""

print "PA External Authn. GetPageForStep jansKey found in request param: %s" % jansKeyParam
return "postlogin.xhtml"

def getNextStep(self, configurationAttributes, requestParameters, step):
return -1

def getLogoutExternalUrl(self, configurationAttributes, requestParameters):
return None

def logout(self, configurationAttributes, requestParameters):
return True
Loading