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

feat: add custom Github External Authenticator script for ADS #3625 #3626

Merged
merged 1 commit into from
Jan 15, 2023
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,286 @@
# Janssen Project software is available under the Apache 2.0 License (2004). See http://www.apache.org/licenses/ for full text.
# Copyright (c) 2020, Janssen Project
#
# Author: Arnab Dutta
#

from io.jans.as.common.model.common import User
from io.jans.as.model.jwt import Jwt
from io.jans.as.server.service import AuthenticationService
from io.jans.as.common.service.common import UserService
from io.jans.as.server.service.net import HttpService
from io.jans.as.server.security import Identity
from io.jans.as.server.util import ServerUtil
from io.jans.orm import PersistenceEntryManager
from io.jans.as.persistence.model.configuration import GluuConfiguration
from io.jans.model.custom.script.type.auth import PersonAuthenticationType
from io.jans.service.cdi.util import CdiUtil
from io.jans.util import StringHelper

from io.jans.jsf2.service import FacesService
from java.util import Arrays, UUID

import json
import sys
import datetime
import urllib

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

def init(self, customScript, configurationAttributes):
print "GitHub. Initialization"

# read config from github_creds_file
github_creds_file = configurationAttributes.get("github_creds_file").getValue2()
f = open(github_creds_file, 'r')
try:
creds = json.loads(f.read())
print creds
except:
print "GitHub: Initialization. Failed to load creds from file:", github_creds_file
print "Exception: ", sys.exc_info()[1]
return False
finally:
f.close()

self.op_server = str(creds["op_server"])
self.client_id = str(creds["client_id"])
self.client_secret = str(creds["client_secret"])
self.authorization_uri = str(creds["authorization_uri"])
self.token_uri = str(creds["token_uri"])
self.userinfo_uri = str(creds["userinfo_uri"])
self.redirect_uri = str(creds["redirect_uri"])
self.scope = str(creds["scope"])
self.title = str(creds["title"])
self.auto_redirect = creds["auto_redirect"]

print "GitHub: Initialized successfully"

return True

def destroy(self, configurationAttributes):
print "GitHub. Destroy"
print "GitHub. 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 "GitHub: authenticate called for step %s" % str(step)
identity = CdiUtil.bean(Identity)
authenticationService = CdiUtil.bean(AuthenticationService)

if step == 1:
# Get Access Token
tokenResponse = self.getToken(requestParameters)
if tokenResponse is None:
return False

# Get User Info
userInfo = self.getUserInfo(tokenResponse["access_token"])
foundUser = self.addUser(userInfo)
if foundUser is None:
return False

identity.setWorkingParameter("gihub_username", userInfo["login"])
identity.setWorkingParameter("gihub_access_token", tokenResponse["access_token"])

print "GitHub: Successfully authenticated"

loggedIn = authenticationService.authenticate(foundUser.getUserId())
print "GitHub: Authentication: %s" % str(loggedIn)
return loggedIn
elif step == 2:
# The second step is used for storing GitHub Repository path from xhtml form to `adsUserGithubRepo` custom User attribute in persistence
print requestParameters
repositoryPath = ServerUtil.getFirstValue(requestParameters, "GithubDetailsForm:repositoryPath")
print repositoryPath
if repositoryPath is None:
print "GitHub repository path is blank"
return False

print "GitHub. Trying to save GitHub repository path %s" % repositoryPath

authenticationService = CdiUtil.bean(AuthenticationService)
user = authenticationService.getAuthenticatedUser()
user.setAttribute("adsUserGithubRepo", repositoryPath)

userService = CdiUtil.bean(UserService)
userService.updateUser(user)

identity.setWorkingParameter("github_repository_Path", user.getAttribute("adsUserGithubRepo"))
return True



def prepareForStep(self, configurationAttributes, requestParameters, step):
print "GitHub: prepareForStep called for step %s" % str(step)
identity = CdiUtil.bean(Identity)
if step == 1:
# redirect to external OIDC server

redirect_url_elements = [self.authorization_uri,
"?response_type=code id_token",
"&client_id=", self.client_id,
"&scope=", self.scope,
"&redirect_uri=", self.redirect_uri]
redirect_url = "".join(redirect_url_elements)

if self.auto_redirect:
facesService = CdiUtil.bean(FacesService)
facesService.redirectToExternalURL(redirect_url)
else:
identity.setWorkingParameter("oidc_redirect_uri", redirect_url)
identity.setWorkingParameter("oidc_title", self.title)

elif step == 2:
authenticationService = CdiUtil.bean(AuthenticationService)
user = authenticationService.getAuthenticatedUser()
if user == None:
print "GitHub. Authenticate for step 2. Failed to determine username"
return False
if user.getAttribute("adsUserGithubRepo") is not None and len(user.getAttribute("adsUserGithubRepo")) > 0:
identity.setWorkingParameter("github_repository_Path", user.getAttribute("adsUserGithubRepo").replace("[", "").replace("]", ""))

return True

def getExtraParametersForStep(self, configurationAttributes, step):
print "GitHub: getExtraParametersForStep called for step %s" % str(step)
return Arrays.asList("gihub_username", "gihub_access_token", "github_repository_Path")

def getCountAuthenticationSteps(self, configurationAttributes):
print "GitHub: getCountAuthenticationSteps called"
return 2

def getPageForStep(self, configurationAttributes, step):
print "GitHub: getPageForStep called for step %s" % str(step)
if(step == 1):
return "/auth/github/login.xhtml"
elif(step == 2):
return "/auth/github/detailsForm.xhtml"
return ""

def getNextStep(self, configurationAttributes, requestParameters, step):
print "GitHub: getNextStep called for step %s" % str(step)
return -1

def getLogoutExternalUrl(self, configurationAttributes, requestParameters):
print "GitHub: Get external logout URL call"
return None

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

def generalLogin(self, identity, authenticationService):
print "GitHub: general login"
credentials = identity.getCredentials()
user_name = credentials.getUsername()
user_password = credentials.getPassword()

logged_in = False
if (StringHelper.isNotEmptyString(user_name) and StringHelper.isNotEmptyString(user_password)):
logged_in = authenticationService.authenticate(user_name, user_password)

return logged_in

def getLocalPrimaryKey(self):
entryManager = CdiUtil.bean(PersistenceEntryManager)
config = GluuConfiguration()
config = entryManager.find(config.getClass(), "ou=configuration,o=jans")
# Pick (one) attribute where user id is stored (e.g. uid/mail)
# primaryKey is the primary key on the backend AD / LDAP Server
# localPrimaryKey is the primary key on Janssen. This attr value has been mapped with the primary key attr of the backend AD / LDAP when configuring cache refresh
uid_attr = config.getIdpAuthn().get(0).getConfig().findValue("localPrimaryKey").asText()
print "GitHub: init. uid attribute is '%s'" % uid_attr
return uid_attr

def getToken(self, requestParameters):
print "GitHub: Get Access Token"
oidcCode = ServerUtil.getFirstValue(requestParameters, "code")
httpService = CdiUtil.bean(HttpService)
httpclient = httpService.getHttpsClient()
tokenRequestData = urllib.urlencode({
"code" : oidcCode,
"grant_type" : "authorization_code",
"redirect_uri": self.redirect_uri,
"client_id": self.client_id,
"client_secret": self.client_secret
})

tokenRequestHeaders = { "Content-type" : "application/x-www-form-urlencoded", "Accept" : "application/json" }

resultResponse = httpService.executePost(httpclient, self.token_uri, None, tokenRequestHeaders, tokenRequestData)
httpResponse = resultResponse.getHttpResponse()
httpResponseStatusCode = httpResponse.getStatusLine().getStatusCode()
print "OIDC: token response status code: %s" % httpResponseStatusCode
if str(httpResponseStatusCode) != "200":
print "OIDC: Failed to get token, status code %s" % httpResponseStatusCode
return None

responseBytes = httpService.getResponseContent(httpResponse)
responseString = httpService.convertEntityToString(responseBytes)
tokenResponse = json.loads(responseString)

return tokenResponse

def addUser(self, user):
try:
print "GitHub: Adding user"
userId = user["login"]
userService = CdiUtil.bean(UserService)
foundUser = userService.getUserByAttribute("jansExtUid", "github:"+userId)

if foundUser is None:
print "GitHub: User not found, adding new"
foundUser = User()
foundUser.setAttribute("jansExtUid", "github:"+userId)
foundUser.setAttribute("jansEmail", user["email"])
foundUser.setAttribute("mail", user["email"])
foundUser.setAttribute("displayName", "github:"+userId)
foundUser.setAttribute("givenName", "github:"+userId)
foundUser.setAttribute(self.getLocalPrimaryKey(), userId)
foundUser = userService.addUser(foundUser, True)

return foundUser
except Exception as e:
print e
print "GitHub: Add user Exception: ", sys.exc_info()[1]
return None

def getUserInfo(self, accessToken):
try:
print "GitHub: Get Userinfo"
httpService = CdiUtil.bean(HttpService)
httpclient = httpService.getHttpsClient()
tokenRequestHeaders = { "Authorization" : "Bearer %s" % accessToken, "Accept" : "application/json" }

resultResponse = httpService.executeGet(httpclient, self.userinfo_uri, tokenRequestHeaders)
httpResponse = resultResponse.getHttpResponse()
httpResponseStatusCode = httpResponse.getStatusLine().getStatusCode()
print "GitHub: userinfo response status code: %s" % httpResponseStatusCode
if str(httpResponseStatusCode) != "200":
print "GitHub: Failed to get userinfo, status code %s" % httpResponseStatusCode
return None

responseBytes = httpService.getResponseContent(httpResponse)
responseString = httpService.convertEntityToString(responseBytes)
userinfoResponse = json.loads(responseString)

print userinfoResponse

return userinfoResponse
except Exception as e:
print e
return None
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# Janssen Project software is available under the Apache 2.0 License (2004). See http://www.apache.org/licenses/ for full text.
# Copyright (c) 2020, Janssen Project
#
# Author: Yuriy Movchan
#

from io.jans.as.common.model.common import User
Expand Down Expand Up @@ -137,7 +136,7 @@ def getCountAuthenticationSteps(self, configurationAttributes):
def getPageForStep(self, configurationAttributes, step):
print "GitHub: getPageForStep called for step %s" % str(step)
if(step == 1):
return "/auth/github/github.xhtml"
return "/auth/github/login.xhtml"
return ""

def getNextStep(self, configurationAttributes, requestParameters, step):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:h="http://xmlns.jcp.org/jsf/html"
template="/WEB-INF/incl/layout/login-template.xhtml">
<f:metadata>
<f:viewAction action="#{authenticator.prepareAuthenticationForStep}" if="#{not identity.loggedIn}"/>
</f:metadata>
<ui:define name="head">
<meta name="description" content="Gluu, Inc." />
</ui:define>
<ui:define name="pageTitle">
<h:outputText value="Github Repository Path" />
</ui:define>


<ui:define name="body">
<div class="container">
<h:panelGroup rendered="true">
<div class="login_bx_1"
style="border-radius: 10px; margin-top: 0px; background: white; border: 1px solid #008b8b;">
<div class="row">
<h:messages class="text-center"
style="color:#8b0000;margin:5px;margin-left:20px; font-size:2vw;"
infoClass="text-center" errorClass="text-center" />
</div>
<h:form id="GithubDetailsForm" style="padding:30px;">

<div class="row">
<div class="col-sm-3 col-md-3">
<h:outputText value="Github Repository Path" />
</div>
<div class="col-sm-9 col-md-9">

<h:inputText placeholder="Enter Github Repository Path"
id="repositoryPath" name="repositoryPath" required="true" colMd="10"
labelColMd="2" autocomplete="off"
styleClass="form-control"
style="width:100%"
value="#{repositoryPath}">
</h:inputText>
</div>
</div>

<div class="form-group row">
<div class="col-sm-offset-2 offset-md-2 col-sm-8 col-md-8">
<h:commandButton id="nextButton"
style="background-color: #00BE79; color:white;"
styleClass="btn col-sm-12" value="Next"
iconAwesome="fa-sign-in"
action="#{authenticator.authenticate}"
/>
</div>
</div>

<h:panelGroup layout="block" rendered="#{not empty facesContext.messageList and cookie['X-Correlation-Id'] != null}">
<br/>
<p style="font-size: 0.7em">
<strong>Correlation Id: </strong> <h:outputText value="#{cookie['X-Correlation-Id'].value}" />
</p>
</h:panelGroup>
<h:inputHidden id="platform" />
</h:form>

</div>
</h:panelGroup>
</div>
<script type="text/javascript">
$(document).ready(function () {
var repositoryPath = '${identity.getWorkingParameter('github_repository_Path')}';
document.getElementById("GithubDetailsForm:repositoryPath").value = repositoryPath;
});
</script>

</ui:define>
</ui:composition>
Loading