Skip to content

Commit

Permalink
draft - work in progress
Browse files Browse the repository at this point in the history
  • Loading branch information
Milton-Ch committed Aug 23, 2022
1 parent f69fba7 commit 6ab82b5
Show file tree
Hide file tree
Showing 5 changed files with 506 additions and 0 deletions.
160 changes: 160 additions & 0 deletions external-authn/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# 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?session_id={session_id}&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)
```
![](external-authn-diagram.png)

Follow the instructions below to set up:

## oxAuth Configuration
Above the **ox-auth** record of the **oxAuthConfiguration** table, enable a new attribute on the **oxAuthConfDynamic** field

Example:
```
"authorizationRequestCustomAllowedParameters": [
{
"paramName": "jansKey",
"returnInResponse": false
}
]
```

## 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\\"}"]}'
```

Modify the **oxScript** field by adding the content of the following link: [PersonAuthentication Script](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](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](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)
```

## Enable custom script on the Client
In the table **oxAuthClient** modify the field **oxAttributes** associating the custom scripts configured previously
```
"updateTokenScriptDns": ["inum=UPDT-EA01,ou=scripts,o=gluu"],
"ropcScripts": ["inum=ROPC-EA01,ou=scripts,o=gluu"],
```

And in the field **oxAuthDefaultAcrValues** add this value:
```
{"v": ["pa-external-authn"]}
```

## 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": "eyJjYWxsYmFja191cmwiOiJodHRwczovL2phbnMubG9jYWxob3N0L2phbnMtYXV0aC9yZXN0djEvYXV0aG9yaXplP3Jlc3BvbnNlX3R5cGU9Y29kZSZzZXNzaW9uX2lkPWI1MDFmNzg0LTY0MTAtNDIyYy1iYWQ0LWU0MTNiYTViMjI1NSZyZWRpcmVjdF91cmk9aHR0cHMlM0ElMkYlMkZqYW5zLmxvY2FsaG9zdCUyRmphbnMtYXV0aC1ycCUyRmhvbWUuaHRtJmNsaWVudF9pZD0xNGUzNmUxOC0xZDUxLTQxYWMtYTRjZi1hN2RjNjc3ZjUzYTUiLCJraWQiOiIxNmEyMmIwMy03YjUzLTQxY2QtYWE3OS01YjI1ZjNlY2QzOGNfc2lnX3JzMjU2IiwidHlwIjoiand0IiwiYWxnIjoiUlMyNTYifQ.eyJjYWxsYmFja191cmwiOiJodHRwczovL2phbnMubG9jYWxob3N0L2phbnMtYXV0aC9yZXN0djEvYXV0aG9yaXplP3Jlc3BvbnNlX3R5cGU9Y29kZSZzZXNzaW9uX2lkPWI1MDFmNzg0LTY0MTAtNDIyYy1iYWQ0LWU0MTNiYTViMjI1NSZyZWRpcmVjdF91cmk9aHR0cHMlM0ElMkYlMkZqYW5zLmxvY2FsaG9zdCUyRmphbnMtYXV0aC1ycCUyRmhvbWUuaHRtJmNsaWVudF9pZD0xNGUzNmUxOC0xZDUxLTQxYWMtYTRjZi1hN2RjNjc3ZjUzYTUiLCJhdWQiOiIxNGUzNmUxOC0xZDUxLTQxYWMtYTRjZi1hN2RjNjc3ZjUzYTUiLCJhY3IiOiJzaW1wbGVfcGFzc3dvcmRfYXV0aCIsInN1YiI6InpMM3FrWFVwZE9OOXoxRDNNVUkyRG1CTHd3MzA1RUt4eWY3al8zb0oyaDQiLCJjb2RlIjoiNzg4OTA4ZTAtMmM3MC00YjE1LWFkZmUtYWZlMDBiMTgyMTkxIiwiYW1yIjpbXSwiaXNzIjoiaHR0cHM6Ly9qYW5zLmxvY2FsaG9zdCIsImV4cCI6MTY1OTc1ODA4MywiZ3JhbnQiOiJwYXNzd29yZCIsImlhdCI6MTY1OTc1NDQ4Mywic2lkIjoiODA0OGZjYmYtM2RjNC00YjFhLTgwYjktNmU1NTkzZTJhOWMwIiwib3hPcGVuSURDb25uZWN0VmVyc2lvbiI6Im9wZW5pZGNvbm5lY3QtMS4wIn0.u0sK9Yccf-P0OLQe0cKz75dF3cKQzQt9QpoHbqBsuIgXb4YwzDTWSuVM055USo5iO9P3KUiIGnMs6WPrZa-xcH84oKWsjF4TnAHdtwE_1xSofxDUU1nSdg8V3mRjHmZKWAvSmGtWTJFqTNGJKQsz1xrTpZ4ZAsI8Ey5OUNIhLsS6BIrFixLm3Sjufe4I2hiTtUPXb1PSYkPgHPZI00h8h4NDspPM9syufMbSLi-HN1aTQaJCONw2X-4KnvLGq1utX7qryclW1pq17W2j6Z49CjCW5ZtstK2p2FRoPwnInXNNr6vqSrpWR6pY9Uus7LHZZbg2DWKBASyfdjN6ecQaIw",
"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&session_id=b501f784-6410-422c-bad4-e413ba5b2255&redirect_uri=https%3A%2F%2Fjans.localhost%2Fjans-auth-rp%2Fhome.htm&client_id=14e36e18-1d51-41ac-a4cf-a7dc677f53a5'
```

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
```
Binary file added external-authn/external-authn-diagram.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
128 changes: 128 additions & 0 deletions external-authn/pa-external-authn.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# 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 for step: %s" % step
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 for step: %s" % step

externalContext = CdiUtil.bean(ExternalContext)
sessionId = ServerUtil.getFirstValue(externalContext.getRequestParameterValuesMap(), "session_id")
if (sessionId == None):
# Retrieve sessionId from request parameters 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 parameters" % 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 parameters" % 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 ""

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

0 comments on commit 6ab82b5

Please sign in to comment.