Skip to content

Commit

Permalink
feat(jans-auth-server): #808 sign-in with apple interception script
Browse files Browse the repository at this point in the history
* feat: #808 - SIWA

* feat: #808 Sign-in with Apple interception script
  • Loading branch information
maduvena authored May 4, 2022
1 parent ed8df58 commit c21183a
Show file tree
Hide file tree
Showing 2 changed files with 396 additions and 0 deletions.
197 changes: 197 additions & 0 deletions jans-auth-server/server/src/main/webapp/auth/apple/login.xhtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
<!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}" />
<f:viewParam name="login_hint" value="#{authorizeAction.loginHint}" />
</f:metadata>
<ui:define name="head">
<meta name="description" content="Gluu, Inc." />
</ui:define>
<ui:define name="pageTitle">
<h:outputText value="#{msgs['login.pageTitle']}" />
</ui:define>
<ui:define name="body">
<script type="text/javascript" src="https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js"></script>

<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="loginForm" style="padding:30px;">
<div class="row">
<div class="col-sm-3 col-md-3">
<h:outputText value="#{msgs['login.username']}" />
</div>
<div class="col-sm-9 col-md-9">
<h:inputText placeholder="#{msgs['login.username']}"
id="username" name="username" required="true" colMd="10"
labelColMd="2" autocomplete="off"
value="#{credentials.username}" styleClass="form-control"
style="width:100%">
</h:inputText>
</div>
</div>
<div class="form-group row"></div>
<div class="row">
<div class="col-sm-3 col-md-3">
<h:outputText value="#{msgs['login.password']}" />
</div>
<div class="col-sm-9 col-md-9">
<h:inputSecret placeholder="#{msgs['login.password']}"
colMd="10" id="password" name="password" labelColMd="2"
value="#{credentials.password}" autocomplete="off"
styleClass="form-control" tyle="width:100%">
</h:inputSecret>
</div>
</div>
<div class="form-group row"></div>

<div class="row">
<div class="col-sm-5 col-md-5">
<h:outputLabel styleClass="col-form-label" for="rememberme"
value="#{msgs['login.rememberMe']}" />
</div>
<div class="col-sm-2 col-md-2" style="padding-top: 15px">
<input type="checkbox" value="rememberme" id="rememberme"
name="rememberme" />
</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="loginButton"
style="background-color: #00BE79; color:white;"
styleClass="btn col-sm-12" value=" #{msgs['login.login']}"
onclick="checkRemembeMe()" iconAwesome="fa-sign-in"
action="#{authenticator.authenticate}" />
</div>
</div>
<div class="form-group row">
<div class="col-sm-offset-3 offset-md-3 col-sm-7 col-md-7">
<div class="forgot_link">
<a href="/identity/person/passwordReminder.htm"
style="color: blue;"> <h:outputText
value="#{msgs['login.forgotYourPassword']}" />
</a>
</div>
</div>
</div>
<h:inputHidden id="platform" />
<div id="appleid-signin" data-color="black" data-border="true" data-type="sign in"></div>

</h:form>
<div class="row">
<div align="center" class="col-sm-offset-4 col-sm-9">
<ui:repeat
value="#{identity.getWorkingParameter('download_url').entrySet().toArray()}"
var="_entry">
<ui:param name="app_name" value="#{_entry.key}" />
<ui:param name="app_link" value="#{_entry.value}" />
<h:outputLink style="margin-right:5px;" value="#{app_link}"
title="#{app_name}">
<h:graphicImage value="img/#{app_name}.png" />
</h:outputLink>
</ui:repeat>
</div>
<h:panelGroup layout="block"
rendered="#{external_registration_uri != Null}">
<div class="reg_link">
<a href="#{external_registration_uri}"> <h:outputText
value="Register Now " />
</a>
</div>
</h:panelGroup>
</div>
</div>

</h:panelGroup>
</div>
<script type="text/javascript">



apple_init_obj = {};
apple_init_obj['clientId'] = `${identity.getWorkingParameter('aclient_id')}`;
apple_init_obj['scope'] = 'name email';
apple_init_obj['redirectURI'] = encodeURI("${identity.getWorkingParameter('aredirectURI')}");
apple_init_obj['astate'] = `${identity.getWorkingParameter('astate')}`;
apple_init_obj['anonce'] = `${identity.getWorkingParameter('anonce')}`;

//apple_init_obj['usePopup'] = true;

AppleID.auth.init(apple_init_obj);
</script>
<script type="text/javascript">
$(document).ready(function () {

if (localStorage.chkbx &amp;&amp; localStorage.chkbx != '') {
$('#rememberme').attr('checked', 'checked')
document.getElementById("loginForm:username").value = localStorage.usrname;
} else {
$('#rememberme').removeAttr('checked');
document.getElementById("loginForm:username").value = "";
}

$('#rememberme').click(function() {
checkRemembeMe();
});

fillPlatformField();

var userNameField = document.getElementById("loginForm:username");
var passwordField = document.getElementById("loginForm:password");

passwordField.value = "";
var userName = '#{!empty authorizeAction.loginHint ? authorizeAction.loginHint : ""}';
if (userName) {
userNameField.value = userName;
passwordField.focus();
} else {
userNameField.focus();
}

var displayRegister = #{display_register_action or identity.sessionId.sessionAttributes['display_register_action']};
if (displayRegister) {
var registerButton = document.getElementById("loginForm:registerId");
if (registerButton != null) {
registerButton.style.display = 'inline';
}
}
});

function checkRemembeMe() {
if ($('#rememberme').is(':checked')) {
localStorage.usrname = document.getElementById("loginForm:username").value;
localStorage.chkbx = $('#rememberme').val();
} else {
localStorage.usrname = '';
localStorage.chkbx = '';
}
}

function fillPlatformField() {
try {
re = /^([^\.]+\.[^\.]+)\..+/;
result = re.exec(platform.version);
if (result != null) {
platform.version=result[1];
}
document.getElementById("loginForm:platform").value = JSON.stringify(platform);
} catch (e) {
}
}


</script>

</ui:define>
</ui:composition>
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
# 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: Madhumita Subramaniam
#

from io.jans.as.server.util import ServerUtil
from io.jans.service.cdi.util import CdiUtil
from io.jans.model.security import Identity
from io.jans.as.common.model.common import User
from jakarta.faces.context import FacesContext
from io.jans.as.server.service.net import HttpService
from io.jans.model.custom.script.type.auth import PersonAuthenticationType
from io.jans.as.server.service import AuthenticationService, UserService
from io.jans.orm import PersistenceEntryManager
from io.jans.as.persistence.model.configuration import GluuConfiguration
from io.jans.util import StringHelper
from java.math import BigInteger
from java.security import SecureRandom
import java
import sys
import json
from java.util import UUID
from io.jans.as.model.jws import ECDSASigner;
from io.jans.as.model.jws import RSASigner;
from io.jans.as.model.jwt import Jwt;
from io.jans.as.model.crypto.signature import ECDSAPublicKey;
from io.jans.as.model.crypto.signature import RSAPublicKey;
from io.jans.as.client import JwkClient;
from io.jans.as.model.crypto.signature import AlgorithmFamily;
from java.util import Collections, HashMap, HashSet, ArrayList, Arrays, Date

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

def init(self, customScript, configurationAttributes):
print "Apple Initialization"
if (not configurationAttributes.containsKey("apple_client_id")):
print "Apple. Initialization. Property apple_client_id is not specified"
return False
else:
self.apple_client_id = configurationAttributes.get("apple_client_id").getValue2()

if (not configurationAttributes.containsKey("apple_jwks")):
print "Apple. Initialization. Property apple_jwks is not specified"
return False
else:
self.apple_jwks = configurationAttributes.get("apple_jwks").getValue2()
print "Apple Initialized successfully"
return True

def destroy(self, configurationAttributes):
print "Apple Destroy"
print "Apple 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):
authenticationService = CdiUtil.bean(AuthenticationService)

if (step == 1):
print "Apple Authenticate for step 1"
identity = CdiUtil.bean(Identity)
print requestParameters

id_token = ServerUtil.getFirstValue(requestParameters, "id_token")
if id_token is not None:

apple_id = self.verifyIDToken(id_token)
print apple_id
# if user doesnt exist in persistence, add
foundUser = self.findUserByAppleId(apple_id)
print foundUser
if foundUser is None:
foundUser = User()
foundUser.setAttribute("jansExtUid", "passport-apple:"+apple_id)
foundUser.setAttribute(self.getLocalPrimaryKey(),apple_id)

userService = CdiUtil.bean(UserService)
result = userService.addUser(foundUser, True)
foundUser = self.findUserByAppleId(apple_id)


logged_in = authenticationService.authenticate(apple_id)
return logged_in

else:
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
else:
print "Apple Authenticate Error"
return False

def verifyIDToken(self, appleIdToken):

if appleIdToken is not None:
jwt = Jwt.parse(appleIdToken)
algorithm = jwt.getHeader().getSignatureAlgorithm()
keyId = jwt.getHeader().getKeyId();
userId = jwt.getClaims().getClaimAsString("sub")
print userId
jwksURI = self.apple_jwks;

validSignature = False;
if (algorithm.getFamily() == AlgorithmFamily.RSA) :
publicKey = JwkClient.getRSAPublicKey(jwksURI, keyId);
rsaSigner = RSASigner(algorithm, publicKey);
validSignature = rsaSigner.validate(jwt);
elif (algorithm.getFamily() == AlgorithmFamily.EC) :
publicKey = JwkClient.getECDSAPublicKey(jwksURI, keyId);
ecdsaSigner = ECDSASigner(algorithm, publicKey);
validSignature = ecdsaSigner.validate(jwt);

if(validSignature is True):
return userId
else:
print "Apple. Failed to validate signature of ID_token"
return None

else :
print "Invalid ID token."
return None

def findUserByAppleId(self, apple_id):
userService = CdiUtil.bean(UserService)
return userService.getUserByAttribute("jansExtUid", "passport-apple:"+apple_id)

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 "Casa. init. uid attribute is '%s'" % uid_attr
return

def prepareForStep(self, configurationAttributes, requestParameters, step):
if (step == 1):
print "Apple Prepare for Step 1"
identity = CdiUtil.bean(Identity)
identity.setWorkingParameter("aclient_id",self.apple_client_id)
identity.setWorkingParameter("astate", UUID.randomUUID().toString())
identity.setWorkingParameter("anonce", UUID.randomUUID().toString())

facesContext = CdiUtil.bean(FacesContext)
request = facesContext.getExternalContext().getRequest()
httpService = CdiUtil.bean(HttpService)
url = httpService.constructServerUrl(request) + "/postlogin.htm"

identity.setWorkingParameter("aredirectURI",url)
return True
else:
return False

def getExtraParametersForStep(self, configurationAttributes, step):
list = ArrayList()
list.addAll(Arrays.asList("apple_client_id","astate","anonce"))
return list

def getCountAuthenticationSteps(self, configurationAttributes):
return 1

def getPageForStep(self, configurationAttributes, step):

if(step == 1):
return "/auth/apple/login.xhtml"
return ""

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

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

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

0 comments on commit c21183a

Please sign in to comment.