diff --git a/docker-jans-auth-server/Dockerfile b/docker-jans-auth-server/Dockerfile index f46d2efc4ce..ff6c6747edc 100644 --- a/docker-jans-auth-server/Dockerfile +++ b/docker-jans-auth-server/Dockerfile @@ -70,17 +70,22 @@ RUN mkdir -p /usr/share/java ARG TWILIO_VERSION=7.17.0 RUN wget -q https://repo1.maven.org/maven2/com/twilio/sdk/twilio/${TWILIO_VERSION}/twilio-${TWILIO_VERSION}.jar -O /usr/share/java/twilio.jar + ARG JSMPP_VERSION=2.3.7 RUN wget -q https://repo1.maven.org/maven2/org/jsmpp/jsmpp/${JSMPP_VERSION}/jsmpp-${JSMPP_VERSION}.jar -O /usr/share/java/jsmpp.jar +# This will later be refactored and moved to be pulled from persitence or a central bucket +ARG CASA_VERSION=5.0.0-SNAPSHOT +ARG CASA_BUILD_DATE="2022-01-05 12:52" +RUN wget -q https://jenkins.gluu.org/maven/org/gluu/casa-config/${CASA_VERSION}/casa-config-${CASA_VERSION}.jar -O /usr/share/java/casa-config.jar + # ====== # Python # ====== COPY requirements.txt /app/requirements.txt -RUN pip3 install -U pip \ - && pip3 install --no-cache-dir --default-timeout=300 -r /app/requirements.txt \ - && rm -rf /src/jans-pycloudlib/.git +RUN pip3 install -U pip wheel \ + && pip3 install --no-cache-dir --default-timeout=300 -r /app/requirements.txt # ======= # Cleanup diff --git a/docker-jans-auth-server/libs/casa-external_fido2.py b/docker-jans-auth-server/libs/casa-external_fido2.py index 5d07a08799d..11f3af3c7eb 100644 --- a/docker-jans-auth-server/libs/casa-external_fido2.py +++ b/docker-jans-auth-server/libs/casa-external_fido2.py @@ -1,18 +1,14 @@ -# Based on oxAuth Fido2ExternalAuthenticator.py - -from javax.ws.rs.core import Response -from org.jboss.resteasy.client import ClientResponseFailure -from org.jboss.resteasy.client.exception import ResteasyClientException -from javax.ws.rs.core import Response -from org.gluu.model.custom.script.type.auth import PersonAuthenticationType -from org.gluu.fido2.client import Fido2ClientFactory -from org.gluu.oxauth.security import Identity -from org.gluu.oxauth.service import AuthenticationService, UserService, SessionIdService -from org.gluu.oxauth.util import ServerUtil -from org.gluu.service.cdi.util import CdiUtil -from org.gluu.util import StringHelper +from io.jans.model.custom.script.type.auth import PersonAuthenticationType +from io.jans.fido2.client import Fido2ClientFactory +from io.jans.as.server.security import Identity +from io.jans.as.server.service import AuthenticationService, UserService, SessionIdService +from io.jans.as.server.util import ServerUtil +from io.jans.service.cdi.util import CdiUtil +from io.jans.util import StringHelper from java.util.concurrent.locks import ReentrantLock +from javax.ws.rs import ClientErrorException +from javax.ws.rs.core import Response import java import sys @@ -138,8 +134,8 @@ def prepareForStep(self, configurationAttributes, requestParameters, step): elif (step == 2): print "Fido2. Prepare for step 2" - session_id = CdiUtil.bean(SessionIdService).getSessionIdFromCookie() - if StringHelper.isEmpty(session_id): + session_id = CdiUtil.bean(SessionIdService).getSessionId() + if session_id == None: print "Fido2. Prepare for step 2. Failed to determine session_id" return False @@ -164,7 +160,11 @@ def prepareForStep(self, configurationAttributes, requestParameters, step): assertionService = Fido2ClientFactory.instance().createAssertionService(metaDataConfiguration) assertionRequest = json.dumps({'username': userName}, separators=(',', ':')) assertionResponse = assertionService.authenticate(assertionRequest).readEntity(java.lang.String) - except ClientResponseFailure, ex: + if "internal" in assertionResponse: + identity.setWorkingParameter("platformAuthenticatorAvailable", "true") + else: + identity.setWorkingParameter("platformAuthenticatorAvailable", "false") + except ClientErrorException, ex: print "Fido2. Prepare for step 2. Failed to start assertion flow. Exception:", sys.exc_info()[1] return False else: @@ -174,7 +174,7 @@ def prepareForStep(self, configurationAttributes, requestParameters, step): attestationService = Fido2ClientFactory.instance().createAttestationService(metaDataConfiguration) attestationRequest = json.dumps({'username': userName, 'displayName': userName}, separators=(',', ':')) attestationResponse = attestationService.register(attestationRequest).readEntity(java.lang.String) - except ClientResponseFailure, ex: + except ClientErrorException, ex: print "Fido2. Prepare for step 2. Failed to start attestation flow. Exception:", sys.exc_info()[1] return False @@ -227,18 +227,11 @@ def getMetaDataConfiguration(self): try: self.metaDataConfiguration = metaDataConfigurationService.getMetadataConfiguration().readEntity(java.lang.String) return self.metaDataConfiguration - except ClientResponseFailure, ex: + except ClientErrorException, ex: # Detect if last try or we still get Service Unavailable HTTP error if (attempt == max_attempts) or (ex.getResponse().getResponseStatus() != Response.Status.SERVICE_UNAVAILABLE): raise ex - java.lang.Thread.sleep(3000) - print "Attempting to load metadata: %d" % attempt - except ResteasyClientException, ex: - # Detect if last try or we still get Service Unavailable HTTP error - if attempt == max_attempts: - raise ex - java.lang.Thread.sleep(3000) print "Attempting to load metadata: %d" % attempt finally: diff --git a/docker-jans-auth-server/libs/casa-external_otp.py b/docker-jans-auth-server/libs/casa-external_otp.py index 8b721b88452..2d153113543 100644 --- a/docker-jans-auth-server/libs/casa-external_otp.py +++ b/docker-jans-auth-server/libs/casa-external_otp.py @@ -1,5 +1,3 @@ -# Based on oxAuth OtpExternalAuthenticator.py - # Requires the following custom properties and values: # otp_type: totp/hotp # issuer: Gluu Inc @@ -10,9 +8,6 @@ # qr_options: { width: 400, height: 400 } # registration_uri: https://ce-dev.gluu.org/identity/register -import jarray -import json -import sys from com.google.common.io import BaseEncoding from com.lochbridge.oath.otp import HOTP from com.lochbridge.oath.otp import HOTPValidator @@ -21,18 +16,23 @@ from com.lochbridge.oath.otp.keyprovisioning import OTPAuthURIBuilder from com.lochbridge.oath.otp.keyprovisioning import OTPKey from com.lochbridge.oath.otp.keyprovisioning.OTPKey import OTPType + +from io.jans.jsf2.message import FacesMessages +from io.jans.model.custom.script.type.auth import PersonAuthenticationType +from io.jans.as.server.security import Identity +from io.jans.as.server.service import AuthenticationService, UserService, SessionIdService +from io.jans.as.server.util import ServerUtil +from io.jans.service.cdi.util import CdiUtil +from io.jans.util import StringHelper + from java.security import SecureRandom from java.util import Arrays from java.util.concurrent import TimeUnit from javax.faces.application import FacesMessage -from org.gluu.jsf2.message import FacesMessages -from org.gluu.model.custom.script.type.auth import PersonAuthenticationType -from org.gluu.oxauth.security import Identity -from org.gluu.oxauth.service import AuthenticationService, UserService, SessionIdService -from org.gluu.oxauth.util import ServerUtil -from org.gluu.service.cdi.util import CdiUtil -from org.gluu.util import StringHelper +import jarray +import json +import sys class PersonAuthentication(PersonAuthenticationType): def __init__(self, currentTimeMillis): @@ -351,12 +351,12 @@ def findEnrollments(self, user_name, otpType, skipPrefix = True): result = [] userService = CdiUtil.bean(UserService) - user = userService.getUser(user_name, "oxExternalUid") + user = userService.getUser(user_name, "jansExtUid") if user == None: print "OTP. Find enrollments. Failed to find user" return result - user_custom_ext_attribute = userService.getCustomAttribute(user, "oxExternalUid") + user_custom_ext_attribute = userService.getCustomAttribute(user, "jansExtUid") if user_custom_ext_attribute == None: return result @@ -377,8 +377,8 @@ def findEnrollments(self, user_name, otpType, skipPrefix = True): return result def validateSessionId(self, identity): - session_id = CdiUtil.bean(SessionIdService).getSessionIdFromCookie() - if StringHelper.isEmpty(session_id): + session_id = CdiUtil.bean(SessionIdService).getSessionId() + if session_id == None: print "OTP. Validate session id. Failed to determine session_id" return False @@ -420,7 +420,7 @@ def processOtpAuthentication(self, requestParameters, user_name, identity, otp_a otp_user_external_uid = "hotp:%s;%s" % ( otp_secret_key_encoded, validation_result["movingFactor"] ) # Add otp_user_external_uid to user's external GUID list - find_user_by_external_uid = userService.addUserAttribute(user_name, "oxExternalUid", otp_user_external_uid, True) + find_user_by_external_uid = userService.addUserAttribute(user_name, "jansExtUid", otp_user_external_uid, True) if find_user_by_external_uid != None: return True @@ -433,7 +433,7 @@ def processOtpAuthentication(self, requestParameters, user_name, identity, otp_a otp_user_external_uid = "totp:%s" % otp_secret_key_encoded # Add otp_user_external_uid to user's external GUID list - find_user_by_external_uid = userService.addUserAttribute(user_name, "oxExternalUid", otp_user_external_uid, True) + find_user_by_external_uid = userService.addUserAttribute(user_name, "jansExtUid", otp_user_external_uid, True) if find_user_by_external_uid != None: return True @@ -465,7 +465,7 @@ def processOtpAuthentication(self, requestParameters, user_name, identity, otp_a new_otp_user_external_uid = "hotp:%s;%s" % ( otp_secret_key_encoded, validation_result["movingFactor"] ) # Update moving factor in user entry - find_user_by_external_uid = userService.replaceUserAttribute(user_name, "oxExternalUid", otp_user_external_uid, new_otp_user_external_uid, True) + find_user_by_external_uid = userService.replaceUserAttribute(user_name, "jansExtUid", otp_user_external_uid, new_otp_user_external_uid, True) if find_user_by_external_uid != None: return True @@ -580,7 +580,7 @@ def hasEnrollments(self, configurationAttributes, user): # Both hotp and totp are accounted hasEnrollments = False - values = user.getAttributeValues("oxExternalUid") + values = user.getAttributeValues("jansExtUid") if values != None: for extUid in values: diff --git a/docker-jans-auth-server/libs/casa-external_smpp.py b/docker-jans-auth-server/libs/casa-external_smpp.py deleted file mode 100644 index e696efd56dd..00000000000 --- a/docker-jans-auth-server/libs/casa-external_smpp.py +++ /dev/null @@ -1,408 +0,0 @@ -# Author: Stefan Andersson - -from java.util import Arrays, Date -from java.io import IOException -from java.lang import Enum - -from javax.faces.application import FacesMessage - -from org.gluu.jsf2.message import FacesMessages -from org.gluu.oxauth.security import Identity -from org.gluu.oxauth.service import AuthenticationService, UserService -from org.gluu.oxauth.util import ServerUtil -from org.gluu.model.custom.script.type.auth import PersonAuthenticationType -from org.gluu.service.cdi.util import CdiUtil -from org.gluu.util import StringHelper, ArrayHelper - -from org.jsmpp import InvalidResponseException, PDUException -from org.jsmpp.bean import Alphabet, BindType, ESMClass, GeneralDataCoding, MessageClass, NumberingPlanIndicator, RegisteredDelivery, SMSCDeliveryReceipt, TypeOfNumber -from org.jsmpp.extra import NegativeResponseException, ResponseTimeoutException -from org.jsmpp.session import BindParameter, SMPPSession -from org.jsmpp.util import AbsoluteTimeFormatter, TimeFormatter -import random - - -class SmppAttributeError(Exception): - pass - - -class PersonAuthentication(PersonAuthenticationType): - def __init__(self, currentTimeMillis): - self.currentTimeMillis = currentTimeMillis - - def get_and_parse_smpp_config(self, config, attribute, _type = None, convert = False, optional = False, default_desc = None): - try: - value = config.get(attribute).getValue2() - except: - if default_desc: - default_desc = " using default '{}'".format(default_desc) - else: - default_desc = "" - - if optional: - raise SmppAttributeError("SMPP missing optional configuration attribute '{}'{}".format(attribute, default_desc)) - else: - raise SmppAttributeError("SMPP missing required configuration attribute '{}'".format(attribute)) - - if _type and issubclass(_type, Enum): - try: - return getattr(_type, value) - except AttributeError: - raise SmppAttributeError("SMPP could not find attribute '{}' in {}".format(attribute, _type)) - - if convert: - try: - value = int(value) - except AttributeError: - try: - value = int(value, 16) - except AttributeError: - raise SmppAttributeError("SMPP could not parse value '{}' of attribute '{}'".format(value, attribute)) - - return value - - def init(self, customScript, configurationAttributes): - print("SMPP Initialization") - - self.TIME_FORMATTER = AbsoluteTimeFormatter() - - self.SMPP_SERVER = None - self.SMPP_PORT = None - - self.SYSTEM_ID = None - self.PASSWORD = None - - # Setup some good defaults for TON, NPI and source (from) address - # TON (Type of Number), NPI (Number Plan Indicator) - self.SRC_ADDR_TON = TypeOfNumber.ALPHANUMERIC # Alphanumeric - self.SRC_ADDR_NPI = NumberingPlanIndicator.ISDN # ISDN (E163/E164) - self.SRC_ADDR = "Gluu OTP" - - # Don't touch these unless you know what your doing, we don't handle number reformatting for - # any other type than international. - self.DST_ADDR_TON = TypeOfNumber.INTERNATIONAL # International - self.DST_ADDR_NPI = NumberingPlanIndicator.ISDN # ISDN (E163/E164) - - # Priority flag and data_coding bits - self.PRIORITY_FLAG = 3 # Very Urgent (ANSI-136), Emergency (IS-95) - self.DATA_CODING_ALPHABET = Alphabet.ALPHA_DEFAULT # SMS default alphabet - self.DATA_CODING_MESSAGE_CLASS = MessageClass.CLASS1 # EM (Mobile Equipment (mobile memory), normal message - - # Required server settings - try: - self.SMPP_SERVER = self.get_and_parse_smpp_config(configurationAttributes, "smpp_server") - except SmppAttributeError as e: - print(e) - - try: - self.SMPP_PORT = self.get_and_parse_smpp_config(configurationAttributes, "smpp_port", convert = True) - except SmppAttributeError as e: - print(e) - - if None in (self.SMPP_SERVER, self.SMPP_PORT): - print("SMPP smpp_server and smpp_port is empty, will not enable SMPP service") - return False - - # Optional system_id and password for bind auth - try: - self.SYSTEM_ID = self.get_and_parse_smpp_config(configurationAttributes, "system_id", optional = True) - except SmppAttributeError as e: - print(e) - - try: - self.PASSWORD = self.get_and_parse_smpp_config(configurationAttributes, "password", optional = True) - except SmppAttributeError as e: - print(e) - - if None in (self.SYSTEM_ID, self.PASSWORD): - print("SMPP Authentication disabled") - - # From number and to number settings - try: - self.SRC_ADDR_TON = self.get_and_parse_smpp_config( - configurationAttributes, - "source_addr_ton", - _type = TypeOfNumber, - optional = True, - default_desc = self.SRC_ADDR_TON - ) - except SmppAttributeError as e: - print(e) - - try: - self.SRC_ADDR_NPI = self.get_and_parse_smpp_config( - configurationAttributes, - "source_addr_npi", - _type = NumberingPlanIndicator, - optional = True, - default_desc = self.SRC_ADDR_NPI - ) - except SmppAttributeError as e: - print(e) - - try: - self.SRC_ADDR = self.get_and_parse_smpp_config( - configurationAttributes, - "source_addr", - optional = True, - default_desc = self.SRC_ADDR - ) - except SmppAttributeError as e: - print(e) - - try: - self.DST_ADDR_TON = self.get_and_parse_smpp_config( - configurationAttributes, - "dest_addr_ton", - _type = TypeOfNumber, - optional = True, - default_desc = self.DST_ADDR_TON - ) - except SmppAttributeError as e: - print(e) - - try: - self.DST_ADDR_NPI = self.get_and_parse_smpp_config( - configurationAttributes, - "dest_addr_npi", - _type = NumberingPlanIndicator, - optional = True, - default_desc = self.DST_ADDR_NPI - ) - except SmppAttributeError as e: - print(e) - - # Priority flag and data coding, don't touch these unless you know what your doing... - try: - self.PRIORITY_FLAG = self.get_and_parse_smpp_config( - configurationAttributes, - "priority_flag", - convert = True, - optional = True, - default_desc = "3 (Very Urgent, Emergency)" - ) - except SmppAttributeError as e: - print(e) - - try: - self.DATA_CODING_ALPHABET = self.get_and_parse_smpp_config( - configurationAttributes, - "data_coding_alphabet", - _type = Alphabet, - optional = True, - default_desc = self.DATA_CODING_ALPHABET - ) - except SmppAttributeError as e: - print(e) - - try: - self.DATA_CODING_MESSAGE_CLASS = self.get_and_parse_smpp_config( - configurationAttributes, - "data_coding_alphabet", - _type = MessageClass, - optional = True, - default_desc = self.DATA_CODING_MESSAGE_CLASS - ) - except SmppAttributeError as e: - print(e) - - print("SMPP Initialized successfully") - return True - - def destroy(self, configurationAttributes): - print("SMPP Destroyed successfully") - return True - - def getApiVersion(self): - return 11 - - def getAuthenticationMethodClaims(self, configurationAttributes): - return None - - def isValidAuthenticationMethod(self, usageType, configurationAttributes): - return True - - def getAlternativeAuthenticationMethod(self, usageType, configurationAttributes): - return None - - def authenticate(self, configurationAttributes, requestParameters, step): - print("SMPP Authenticate for step {}".format(step)) - - identity = CdiUtil.bean(Identity) - authenticationService = CdiUtil.bean(AuthenticationService) - user = authenticationService.getAuthenticatedUser() - - if step == 1: - if not user: - credentials = identity.getCredentials() - user_name = credentials.getUsername() - user_password = credentials.getPassword() - - if StringHelper.isNotEmptyString(user_name) and StringHelper.isNotEmptyString(user_password): - authenticationService.authenticate(user_name, user_password) - user = authenticationService.getAuthenticatedUser() - - if not user: - return False - - numbers = self.getNumbers(user) - if not numbers: - return False - else: - # Generate Random six digit code - code = random.randint(100000, 999999) - identity.setWorkingParameter("randCode", code) - - if len(numbers) == 1: - return self.sendMessage(numbers[0], str(code)) - else: - chopped = [number[-4:] for number in numbers] - - # converting to comma-separated list (identity does not remember lists) - identity.setWorkingParameter("numbers", ",".join(numbers)) - identity.setWorkingParameter("choppedNos", ",".join(chopped)) - return True - else: - if not user: - return False - - session_attributes = identity.getSessionId().getSessionAttributes() - code = session_attributes.get("randCode") - numbers = session_attributes.get("numbers") - - if step == 2 and numbers: - # Means that the selection number page was used - idx = ServerUtil.getFirstValue(requestParameters, "OtpSmsloginForm:indexOfNumber") - if idx and code: - number = numbers.split(",")[int(idx)] - return self.sendMessage(number, str(code)) - else: - return False - - form_passcode = ServerUtil.getFirstValue(requestParameters, "OtpSmsloginForm:passcode") - if form_passcode and code == form_passcode: - print("SMPP authenticate. 6-digit code matches with code sent via SMS") - return True - else: - facesMessages = CdiUtil.bean(FacesMessages) - facesMessages.setKeepMessages() - facesMessages.clear() - facesMessages.add(FacesMessage.SEVERITY_ERROR, "Wrong code entered") - return False - - def getNumbers(self, user): - numbers = set() - - tmp = user.getAttributeValues("mobile") - if tmp: - for t in tmp: - numbers.add(t) - - return list(numbers) - - def prepareForStep(self, configurationAttributes, requestParameters, step): - print("SMPP Prepare for Step {}".format(step)) - return True - - def getExtraParametersForStep(self, configurationAttributes, step): - if step > 1: - return Arrays.asList("randCode", "numbers", "choppedNos") - return None - - def getCountAuthenticationSteps(self, configurationAttributes): - if not CdiUtil.bean(Identity).getWorkingParameter("numbers"): - return 2 - else: - return 3 - - def getPageForStep(self, configurationAttributes, step): - print("SMPP getPageForStep called {}".format(step)) - print("SMPP Numbers are {}".format(CdiUtil.bean(Identity).getWorkingParameter("numbers"))) - - def_page = "/casa/otp_sms.xhtml" - if step == 2: - if not CdiUtil.bean(Identity).getWorkingParameter("numbers"): - return def_page - else: - return "/casa/otp_sms_prompt.xhtml" - elif step == 3: - return def_page - - return "" - - def logout(self, configurationAttributes, requestParameters): - return True - - def hasEnrollments(self, configurationAttributes, user): - return len(self.getNumbers(user)) > 0 - - def sendMessage(self, number, code): - status = False - session = SMPPSession() - session.setTransactionTimer(10000) - - # We only handle international destination number reformatting. - # All others may vary by configuration decisions taken on SMPP - # server side which we have no clue about. - if self.DST_ADDR_TON == TypeOfNumber.INTERNATIONAL and number.startswith("+"): - number = number[1:] - - try: - print("SMPP Connecting") - reference_id = session.connectAndBind( - self.SMPP_SERVER, - self.SMPP_PORT, - BindParameter( - BindType.BIND_TX, - self.SYSTEM_ID, - self.PASSWORD, - None, - self.SRC_ADDR_TON, - self.SRC_ADDR_NPI, - None - ) - ) - print("SMPP Connected to server with system id {}".format(reference_id)) - - try: - message_id = session.submitShortMessage( - "CMT", - self.SRC_ADDR_TON, - self.SRC_ADDR_NPI, - self.SRC_ADDR, - self.DST_ADDR_TON, - self.DST_ADDR_NPI, - number, - ESMClass(), - 0, - self.PRIORITY_FLAG, - self.TIME_FORMATTER.format(Date()), - None, - RegisteredDelivery(SMSCDeliveryReceipt.DEFAULT), - 0, - GeneralDataCoding( - self.DATA_CODING_ALPHABET, - self.DATA_CODING_MESSAGE_CLASS, - False - ), - 0, - code + " is your passcode to access your account" - ) - print("SMPP Message '{}' sent to #{} with message id {}".format(code, number, message_id)) - status = True - except PDUException as e: - print("SMPP Invalid PDU parameter: {}".format(e)) - except ResponseTimeoutException as e: - print("SMPP Response timeout: {}".format(e)) - except InvalidResponseException as e: - print("SMPP Receive invalid response: {}".format(e)) - except NegativeResponseException as e: - print("SMPP Receive negative response: {}".format(e)) - except IOException as e: - print("SMPP IO error occured: {}".format(e)) - finally: - session.unbindAndClose() - except IOException as e: - print("SMPP Failed connect and bind to host: {}".format(e)) - - return status diff --git a/docker-jans-auth-server/libs/casa-external_super_gluu.py b/docker-jans-auth-server/libs/casa-external_super_gluu.py index e7831b7cc78..51b0d841a99 100644 --- a/docker-jans-auth-server/libs/casa-external_super_gluu.py +++ b/docker-jans-auth-server/libs/casa-external_super_gluu.py @@ -2,25 +2,27 @@ from com.google.android.gcm.server import Sender, Message from com.notnoop.apns import APNS -from java.util import Arrays -from org.apache.http.params import CoreConnectionPNames -from org.gluu.service.cdi.util import CdiUtil -from org.gluu.oxauth.security import Identity -from org.gluu.model.custom.script.type.auth import PersonAuthenticationType -from org.gluu.oxauth.model.config import ConfigurationFactory -from org.gluu.oxauth.service import AuthenticationService, UserService, SessionIdService -from org.gluu.oxauth.service.common import EncryptionService -from org.gluu.oxauth.service.fido.u2f import DeviceRegistrationService -from org.gluu.oxauth.service.net import HttpService -from org.gluu.oxauth.util import ServerUtil -from org.gluu.util import StringHelper -from org.gluu.service import MailService -from org.gluu.oxauth.service.push.sns import PushPlatform, PushSnsService -from org.gluu.oxnotify.client import NotifyClientFactory + +from io.jans.service.cdi.util import CdiUtil +from io.jans.as.server.security import Identity +from io.jans.model.custom.script.type.auth import PersonAuthenticationType +from io.jans.as.model.config import ConfigurationFactory +from io.jans.as.server.service import AuthenticationService, UserService, SessionIdService +from io.jans.as.service.common import EncryptionService +from io.jans.as.service.fido.u2f import DeviceRegistrationService +from io.jans.as.service.net import HttpService +from io.jans.as.server.util import ServerUtil +from io.jans.util import StringHelper +from io.jans.service import MailService +from io.jans.as.service.push.sns import PushPlatform, PushSnsService +from io.jans.oxnotify.client import NotifyClientFactory + from java.util import Arrays, HashMap, IdentityHashMap, Date from java.time import ZonedDateTime from java.time.format import DateTimeFormatter +from org.apache.http.params import CoreConnectionPNames + try: from org.gluu.oxd.license.client.js import Product from org.gluu.oxd.license.validator import LicenseValidator @@ -128,7 +130,7 @@ def init(self, customScript, configurationAttributes): # Validate license try: self.license_content = LicenseValidator.validate(license["public-key"], license["public-password"], license["license-password"], license["license"], - Product.SUPER_CN, Date()) + Product.SUPER_GLUU, Date()) self.valid_license = self.license_content.isValid() except: print "Super-Gluu. Initialization. Failed to validate license. Exception: ", sys.exc_info()[1] @@ -347,8 +349,8 @@ def prepareForStep(self, configurationAttributes, requestParameters, step): print "Super-Gluu. Prepare for step 1" if self.oneStep: #This branch will never be taken (see note in getExtraParametersForStep) - session_id = CdiUtil.bean(SessionIdService).getSessionIdFromCookie() - if StringHelper.isEmpty(session_id): + session_id = CdiUtil.bean(SessionIdService).getSessionId() + if session_id == None: print "Super-Gluu. Prepare for step 2. Failed to determine session_id" return False @@ -386,8 +388,8 @@ def prepareForStep(self, configurationAttributes, requestParameters, step): print "Super-Gluu. Prepare for step 2. Request was generated already" return True - session_id = CdiUtil.bean(SessionIdService).getSessionIdFromCookie() - if StringHelper.isEmpty(session_id): + session_id = CdiUtil.bean(SessionIdService).getSessionId() + if session_id == None: print "Super-Gluu. Prepare for step 2. Failed to determine session_id" return False @@ -1073,7 +1075,7 @@ def hasEnrollments(self, configurationAttributes, user): inum = user.getAttribute("inum") devRegService = CdiUtil.bean(DeviceRegistrationService) app_id = configurationAttributes.get("application_id").getValue2() - userDevices = devRegService.findUserDeviceRegistrations(inum, app_id, "oxStatus") + userDevices = devRegService.findUserDeviceRegistrations(inum, app_id, "jansStatus") hasDevices = False for device in userDevices: diff --git a/docker-jans-auth-server/libs/casa-external_twilio_sms.py b/docker-jans-auth-server/libs/casa-external_twilio_sms.py index 680c9cc5769..1c50f2e1097 100644 --- a/docker-jans-auth-server/libs/casa-external_twilio_sms.py +++ b/docker-jans-auth-server/libs/casa-external_twilio_sms.py @@ -1,23 +1,19 @@ -# This is a modified version of original twilio_sms Gluu's script to work with Casa - -from java.util import Arrays - -from javax.faces.application import FacesMessage - -from org.gluu.jsf2.message import FacesMessages -from org.gluu.oxauth.security import Identity -from org.gluu.oxauth.service import UserService, AuthenticationService -from org.gluu.oxauth.util import ServerUtil -from org.gluu.model.custom.script.type.auth import PersonAuthenticationType -from org.gluu.service.cdi.util import CdiUtil -from org.gluu.util import StringHelper, ArrayHelper - from com.google.common.base import Joiner - from com.twilio import Twilio import com.twilio.rest.api.v2010.account.Message as TwMessage from com.twilio.type import PhoneNumber +from java.util import Arrays +from javax.faces.application import FacesMessage + +from io.jans.jsf2.message import FacesMessages +from io.jans.as.server.security import Identity +from io.jans.as.server.service import UserService, AuthenticationService +from io.jans.as.server.util import ServerUtil +from io.jans.model.custom.script.type.auth import PersonAuthenticationType +from io.jans.service.cdi.util import CdiUtil +from io.jans.util import StringHelper, ArrayHelper + import random import sys @@ -164,6 +160,9 @@ def hasEnrollments(self, configurationAttributes, user): def sendMessage(self, code, numb): try: + if numb[:1] != "+": + numb = "+" + numb + print "TwilioSMS. Sending SMS message (%s) to %s" % (code, numb) msg = "%s is your passcode to access your account" % code message = TwMessage.creator(PhoneNumber(numb), PhoneNumber(self.from_no), msg).create() diff --git a/docker-jans-auth-server/libs/casa-external_u2f.py b/docker-jans-auth-server/libs/casa-external_u2f.py index 24e1c0cf733..32b8864a745 100644 --- a/docker-jans-auth-server/libs/casa-external_u2f.py +++ b/docker-jans-auth-server/libs/casa-external_u2f.py @@ -1,20 +1,18 @@ -# Based on oxAuth U2fExternalAuthenticator.py +from io.jans.model.custom.script.type.auth import PersonAuthenticationType +from io.jans.as.client.fido.u2f import FidoU2fClientFactory +from io.jans.as.model.config import Constants +from io.jans.as.server.security import Identity +from io.jans.as.server.service import AuthenticationService, SessionIdService, UserService +from io.jans.as.server.service.fido.u2f import DeviceRegistrationService +from io.jans.as.server.util import ServerUtil +from io.jans.service.cdi.util import CdiUtil +from io.jans.util import StringHelper + +from javax.ws.rs import ClientErrorException, WebApplicationException +from javax.ws.rs.core import Response import java import sys -from javax.ws.rs.core import Response -from org.jboss.resteasy.client import ClientResponseFailure -from org.jboss.resteasy.client.exception import ResteasyClientException -from org.gluu.model.custom.script.type.auth import PersonAuthenticationType -from org.gluu.oxauth.client.fido.u2f import FidoU2fClientFactory -from org.gluu.oxauth.model.config import Constants -from org.gluu.oxauth.security import Identity -from org.gluu.oxauth.service import AuthenticationService, SessionIdService -from org.gluu.oxauth.service.common import UserService -from org.gluu.oxauth.service.fido.u2f import DeviceRegistrationService -from org.gluu.oxauth.util import ServerUtil -from org.gluu.service.cdi.util import CdiUtil -from org.gluu.util import StringHelper class PersonAuthentication(PersonAuthenticationType): @@ -36,16 +34,9 @@ def init(self, customScript, configurationAttributes): try: self.metaDataConfiguration = metaDataConfigurationService.getMetadataConfiguration() break - except ClientResponseFailure, ex: - # Detect if last try or we still get Service Unavailable HTTP error - if (attempt == max_attempts) or (ex.getResponse().getResponseStatus() != Response.Status.SERVICE_UNAVAILABLE): - raise ex - - java.lang.Thread.sleep(3000) - print "Attempting to load metadata: %d" % attempt - except ResteasyClientException, ex: + except WebApplicationException, ex: # Detect if last try or we still get Service Unavailable HTTP error - if attempt == max_attempts: + if (attempt == max_attempts) or (ex.getResponse().getStatus() != Response.Status.SERVICE_UNAVAILABLE.getStatusCode()): raise ex java.lang.Thread.sleep(3000) @@ -150,8 +141,8 @@ def prepareForStep(self, configurationAttributes, requestParameters, step): elif (step == 2): print "U2F. Prepare for step 2" - session_id = CdiUtil.bean(SessionIdService).getSessionIdFromCookie() - if StringHelper.isEmpty(session_id): + session_id = CdiUtil.bean(SessionIdService).getSessionId() + if session_id == None: print "U2F. Prepare for step 2. Failed to determine session_id" return False @@ -177,15 +168,15 @@ def prepareForStep(self, configurationAttributes, requestParameters, step): try: authenticationRequestService = FidoU2fClientFactory.instance().createAuthenticationRequestService(self.metaDataConfiguration) - authenticationRequest = authenticationRequestService.startAuthentication(user.getUserId(), None, u2f_application_id, session_id) - except ClientResponseFailure, ex: + authenticationRequest = authenticationRequestService.startAuthentication(user.getUserId(), None, u2f_application_id, session_id.getId()) + except ClientErrorException, ex: if (ex.getResponse().getResponseStatus() != Response.Status.NOT_FOUND): print "U2F. Prepare for step 2. Failed to start authentication workflow. Exception:", sys.exc_info()[1] return False else: print "U2F. Prepare for step 2. Call FIDO U2F in order to start registration workflow" registrationRequestService = FidoU2fClientFactory.instance().createRegistrationRequestService(self.metaDataConfiguration) - registrationRequest = registrationRequestService.startRegistration(user.getUserId(), u2f_application_id, session_id) + registrationRequest = registrationRequestService.startRegistration(user.getUserId(), u2f_application_id, session_id.getId()) identity.setWorkingParameter("fido_u2f_authentication_request", ServerUtil.asJson(authenticationRequest)) identity.setWorkingParameter("fido_u2f_registration_request", ServerUtil.asJson(registrationRequest)) @@ -221,7 +212,7 @@ def hasEnrollments(self, configurationAttributes, user): inum = user.getAttribute("inum") devRegService = CdiUtil.bean(DeviceRegistrationService) app_id = configurationAttributes.get("u2f_application_id").getValue2() - userDevices = devRegService.findUserDeviceRegistrations(inum, app_id, "oxStatus") + userDevices = devRegService.findUserDeviceRegistrations(inum, app_id, "jansStatus") hasDevices = False for device in userDevices: diff --git a/docker-jans-auth-server/libs/duo_web.py b/docker-jans-auth-server/libs/duo_web.py deleted file mode 100644 index 2bef248889c..00000000000 --- a/docker-jans-auth-server/libs/duo_web.py +++ /dev/null @@ -1,114 +0,0 @@ -# -# duo_web.py -# -# Copyright (c) 2011 Duo Security -# All rights reserved, all wrongs reversed. -# - -import base64 -import hashlib -import hmac -import time - -DUO_PREFIX = 'TX' -APP_PREFIX = 'APP' -AUTH_PREFIX = 'AUTH' - -DUO_EXPIRE = 300 -APP_EXPIRE = 3600 - -IKEY_LEN = 20 -SKEY_LEN = 40 -AKEY_LEN = 40 - -ERR_USER = 'ERR|The username passed to sign_request() is invalid.' -ERR_IKEY = 'ERR|The Duo integration key passed to sign_request() is invalid.' -ERR_SKEY = 'ERR|The Duo secret key passed to sign_request() is invalid.' -ERR_AKEY = 'ERR|The application secret key passed to sign_request() must be at least %s characters.' % AKEY_LEN -ERR_UNKNOWN = 'ERR|An unknown error has occurred.' - -def _hmac_sha1(key, msg): - ctx = hmac.new(key, msg, hashlib.sha1) - return ctx.hexdigest() - -def _sign_vals(key, vals, prefix, expire): - exp = str(int(time.time()) + expire) - - val = '|'.join(vals + [ exp ]) - b64 = base64.b64encode(val) - cookie = '%s|%s' % (prefix, b64) - - sig = _hmac_sha1(key, cookie) - return '%s|%s' % (cookie, sig) - -def _parse_vals(key, val, prefix): - ts = int(time.time()) - u_prefix, u_b64, u_sig = val.split('|') - - sig = _hmac_sha1(key, '%s|%s' % (u_prefix, u_b64)) - if _hmac_sha1(key, sig) != _hmac_sha1(key, u_sig): - return None - - if u_prefix != prefix: - return None - - user, ikey, exp = base64.b64decode(u_b64).split('|') - - if ts >= int(exp): - return None - - return user - -def sign_request(ikey, skey, akey, username): - """Generate a signed request for Duo authentication. - The returned value should be passed into the Duo.init() call - in the rendered web page used for Duo authentication. - - Arguments: - - ikey -- Duo integration key - skey -- Duo secret key - akey -- Application secret key - username -- Primary-authenticated username - """ - if not username: - return ERR_USER - if not ikey or len(ikey) != IKEY_LEN: - return ERR_IKEY - if not skey or len(skey) != SKEY_LEN: - return ERR_SKEY - if not akey or len(akey) < AKEY_LEN: - return ERR_AKEY - - vals = [ username, ikey ] - - try: - duo_sig = _sign_vals(skey, vals, DUO_PREFIX, DUO_EXPIRE) - app_sig = _sign_vals(akey, vals, APP_PREFIX, APP_EXPIRE) - except: - return ERR_UNKNOWN - - return '%s:%s' % (duo_sig, app_sig) - -def verify_response(ikey, skey, akey, sig_response): - """Validate the signed response returned from Duo. - Returns the username of the authenticated user, or None. - - Arguments: - - ikey -- Duo integration key - skey -- Duo secret key - akey -- Application secret key - sig_response -- The signed response POST'ed to the server - """ - try: - auth_sig, app_sig = sig_response.split(':') - auth_user = _parse_vals(skey, auth_sig, AUTH_PREFIX) - app_user = _parse_vals(akey, app_sig, APP_PREFIX) - except: - return None - - if auth_user != app_user: - return None - - return auth_user diff --git a/docker-jans-auth-server/libs/gluu_common.py b/docker-jans-auth-server/libs/gluu_common.py deleted file mode 100644 index 45ed6f09e71..00000000000 --- a/docker-jans-auth-server/libs/gluu_common.py +++ /dev/null @@ -1,710 +0,0 @@ - -from com.google.android.gcm.server import Sender, Message -from com.notnoop.apns import APNS - -from java.time import ZonedDateTime -from java.time.format import DateTimeFormatter -from javax.ws.rs import InternalServerErrorException - -from org.gluu.oxnotify.model import NotifyMetadata -from org.gluu.oxnotify.client import NotifyClientFactory -from org.apache.http.params import CoreConnectionPNames -from org.gluu.oxauth.service import EncryptionService , UserService -from org.gluu.oxauth.service.fido.u2f import DeviceRegistrationService -from org.gluu.oxauth.service.net import HttpService -from org.gluu.oxauth.service.push.sns import PushPlatform, PushSnsService -from org.gluu.service.cdi.util import CdiUtil -from org.gluu.util import StringHelper - - -import json -import sys - -# -# PushNotificationContext Class -# - -class PushNotificationContext: - def __init__(self, appId, superGluuRequest): - - self.appId = appId - self.superGluuRequest = superGluuRequest - self.debugEnabled = False - self.deviceRegistrationService = CdiUtil.bean(DeviceRegistrationService) - self.pushSnsService = CdiUtil.bean(PushSnsService) - self.user = None - self.u2fDevice = None - self.devicePlatform = None - self.pushToken = None - -# -# PushNotificationManager Class -# -class PushNotificationManager: - def __init__(self, serviceMode, credentialsFile): - - self.pushSnsMode = False - self.pushGluuMode = False - self.pushNotificationsEnabled = False - self.titleTemplate = "Super-Gluu" - self.messageTemplate = "Super-Gluu login request to %s" - self.debugEnabled = True - self.httpConnTimeout = 15 * 1000 # in milliseconds - creds = self.loadCredentials(credentialsFile) - if creds == None: - return None - - if StringHelper.equalsIgnoreCase(serviceMode,"sns"): - self.initSnsPushNotifications(creds) - elif StringHelper.equalsIgnoreCase(serviceMode,"gluu"): - self.initGluuPushNotifications(creds) - else: - self.initNativePushNotifications(creds) - - def initSnsPushNotifications(self, creds): - - print "Super-Gluu-Push. SNS push notifications init ..." - self.pushSnsMode = True - try: - sns_creds = creds["sns"] - android_creds = creds["android"]["sns"] - ios_creds = creds["ios"]["sns"] - except: - print "Super-Gluu-Push. Invalid SNS credentials format" - return None - - self.pushAndroidService = None - self.pushAppleService = None - if not (android_creds["enabled"] or ios_creds["enabled"]): - print "Super-Gluu-Push. SNS disabled for all platforms" - return None - - sns_access_key = sns_creds["access_key"] - sns_secret_access_key = sns_creds["secret_access_key"] - sns_region = sns_creds["region"] - - encryptionService = CdiUtil.bean(EncryptionService) - - try: - sns_secret_access_key = encryptionService.decrypt(sns_secret_access_key) - except: - # Ignore exception. Password is not encrypted - print "Super-Gluu-Push. Assuming 'sns_access_key' is not encrypted" - - pushSnsService = CdiUtil.bean(PushSnsService) - pushClient = pushSnsService.createSnsClient(sns_access_key,sns_secret_access_key,sns_region) - - if android_creds["enabled"]: - self.pushAndroidService = pushClient - self.pushAndroidPlatformArn = android_creds["platform_arn"] - print "Super-Gluu-Push. Created SNS android notification service" - - if ios_creds["enabled"]: - self.pushAppleService = pushClient - self.pushApplePlatformArn = ios_creds["platform_arn"] - self.pushAppleServiceProduction = ios_creds["production"] - - - self.pushNotificationsEnabled = self.pushAndroidService != None or self.pushAppleService != None - - - def initGluuPushNotifications(self, creds): - print "Super-Gluu-Push. Gluu push notifications init ... " - - self.pushGluuMode = True - - try: - gluu_conf = creds["gluu"] - android_creds = creds["android"]["gluu"] - ios_creds = creds["ios"]["gluu"] - except: - print "Super-Gluu-Push. Invalid Gluu credentials format" - return None - - self.pushAndroidService = None - self.pushAppleService = None - - if not(android_creds["enabled"] or ios_creds["enabled"]): - print "Super-Gluu-Push. Gluu disabled for all platforms" - return None - - gluu_server_uri = gluu_conf["server_uri"] - notifyClientFactory = NotifyClientFactory.instance() - metadataConfiguration = self.getNotifyMetadata(gluu_server_uri) - if metadataConfiguration == None: - return None - - gluuClient = notifyClientFactory.createNotifyService(metadataConfiguration) - encryptionService = CdiUtil.bean(EncryptionService) - - if android_creds["enabled"]: - gluu_access_key = android_creds["access_key"] - gluu_secret_access_key = android_creds["secret_access_key"] - - try: - gluu_secret_access_key = encryptionService.decrypt(gluu_secret_access_key) - except: - # Ignore exception. Password is not encrypted - print "Super-Gluu-Push. Assuming 'gluu_secret_access_key' is not encrypted" - - self.pushAndroidService = gluuClient - self.pushAndroidServiceAuth = notifyClientFactory.getAuthorization(gluu_access_key,gluu_secret_access_key) - print "Super-Gluu-Push. Created Gluu Android notification service" - - if ios_creds["enabled"]: - gluu_access_key = ios_creds["access_key"] - gluu_secret_access_key = ios_creds["secret_access_key"] - - try: - gluu_secret_access_key = encryptionService.decrypt(gluu_secret_access_key) - except: - # Ignore exception. Password is not encrypted - print "Super-Gluu-Push. Assuming 'gluu_secret_access_key' is not encrypted" - self.pushAppleService = gluuClient - self.pushAppleServiceAuth = notifyClientFactory.getAuthorization(gluu_access_key,gluu_secret_access_key) - print "Super-Gluu-Push. Created Gluu iOS notification service" - - self.pushNotificationsEnabled = self.pushAndroidService != None or self.pushAppleService != None - - - def initNativePushNotifications(self, creds): - print "Super-Gluu-Push. Native push notifications init ... " - try: - android_creds = creds["android"]["gcm"] - ios_creds = creds["ios"]["apns"] - except: - print "Super-Gluu-Push. Invalid credentials format" - return None - - self.pushAndroidService = None - self.pushAppleService = None - - if android_creds["enabled"]: - self.pushAndroidService = Sender(android_creds["api_key"]) - print "Super-Gluu-Push. Created native Android notification service" - - if ios_creds["enabled"]: - p12_file_path = ios_creds["p12_file_path"] - p12_password = ios_creds["p12_password"] - - try: - encryptionService = CdiUtil.bean(EncryptionService) - p12_password = encryptionService.decrypt(p12_password) - except: - # Ignore exception. Password is not encrypted - print "Super-Gluu-Push. Assuming 'p12_password' is not encrypted" - - apnsServiceBuilder = APNS.newService().withCert(p12_file_path,p12_password) - if ios_creds["production"]: - self.pushAppleService = apnsServiceBuilder.withProductionDestination().build() - else: - self.pushAppleService = apnsServiceBuilder.withSandboxDestination().build() - - self.pushAppleServiceProduction = ios_creds["production"] - print "Super-Gluu-Push. Created native iOS notification service" - - self.pushNotificationsEnabled = self.pushAndroidService != None or self.pushAppleService != None - - - def loadCredentials(self, credentialsFile): - print "Super-Gluu-Push. Loading credentials ... " - f = open(credentialsFile,'r') - try: - creds = json.loads(f.read()) - print "Super-Gluu-Push. Credentials loaded successfully" - except: - exception_value = sys.exc_info()[1] - print "Super-Gluu-Push. Loading credentials failed.", exception_value - return None - finally: - f.close() - - return creds - - def getNotifyMetadata(self, gluu_server_uri): - - try: - notifyClientFactory = NotifyClientFactory.instance() - metadataConfigurationService = notifyClientFactory.createMetaDataConfigurationService(gluu_server_uri) - return metadataConfigurationService.getMetadataConfiguration() - except: - exc_value = sys.exc_info()[1] - print "Super-Gluu-Push. Gluu push notification init failed while loading metadata. %s." % exc_value - print "Super-Gluu-Push. Retrying loading metadata using httpService..." - httpService = CdiUtil.bean(HttpService) - http_client = httpService.getHttpsClient() - http_client_params = http_client.getParams() - http_client_params.setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT,self.httpConnTimeout) - notify_service_url = "%s/.well-known/notify-configuration" % gluu_server_uri - notify_service_headers = {"Accept": "application/json"} - try: - http_service_response = httpService.executeGet(http_client,notify_service_url,notify_service_headers) - if http_service_response == None: - print "Super-Gluu-Push. Loading metadata using httpService failed. Nil http_service_response" - return None - http_response = http_service_response.getHttpResponse() - except: - print "Super-Gluu-Push. Loading metadata using httpService failed. %s." % sys.exc_info()[1] - return None - - try: - if not httpService.isResponseStastusCodeOk(http_response): - http_error_str = str(http_response.getStatusLine().getStatusCode()) - print "Super-Gluu-Push. Loading metadata using httpService failed with http code %s." % http_error_str - httpService.consume(http_response) - return None - resp_bytes = httpService.getResponseContent(http_response) - resp_str = httpService.convertEntityToString(resp_bytes) - httpService.consume(http_response) - except: - print "Super-Gluu-Push. Loading metadata using httpService failed. %s." % sys.exc_info()[1] - return None - finally: - http_service_response.closeConnection() - - if resp_str == None: - print "Super-Gluu-Push. Loading metadata using httpService failed.Empty response from server" - return None - - json_resp = json.loads(resp_str) - if ('version' not in json_resp) or ('issuer' not in json_resp): - print "Super-Gluu-Push. Loading metadata using httpService failed. Invalid json response %s." % json_resp - return None - - if ('notify_endpoint' not in json_resp) and ('notifyEndpoint' not in json_resp): - print "Super-Gluu-Push. Loading metadata using httpService failed. Invalid json response %s." % json_resp - return None - - notifyMeta = NotifyMetadata() - notifyMeta.setVersion(json_resp['version']) - notifyMeta.setIssuer(json_resp['issuer']) - if 'notify_endpoint' in json_resp: - notifyMeta.setNotifyEndpoint(json_resp['notify_endpoint']) - elif 'notifyEndpoint' in json_resp: - notifyMeta.setNotifyEndpoint(json_resp['notifyEndpoint']) - print "Super-Gluu-Push. Metadata loaded using httpService successfully" - return notifyMeta - - def sendPushNotification(self, user, app_id, super_gluu_request): - try: - return self.sendPushNotificationImpl(user, app_id, super_gluu_request) - except InternalServerErrorException as is_error: - print "Super-Gluu-Push. Failed to send push notification : '%s'" % is_error.getMessage() - return 0 - except: - exception_value = sys.exc_info()[1] - print "Super-Gluu-Push. Failed to send push notification :" % exception_value - return 0 - - def sendPushNotificationImpl(self, user, app_id, super_gluu_request): - - if not self.pushNotificationsEnabled: - print "Super-Gluu-Push. Push notifications are disabled" - return None - - user_name = user.getUserId() - print "Super-Gluu-Push. Sending push notification to user '%s' devices" % user_name - - userService = CdiUtil.bean(UserService) - deviceRegistrationService = CdiUtil.bean(DeviceRegistrationService) - - user_inum = userService.getUserInum(user_name) - - u2f_device_list = deviceRegistrationService.findUserDeviceRegistrations(user_inum, app_id, - "oxId","oxDeviceData","oxDeviceNotificationConf") - - send_ios = 0 - send_android = 0 - if u2f_device_list.size() > 0: - for u2f_device in u2f_device_list: - print "Super-Gluu-Push. Send device notification to device" - device_push_result = self.sendDevicePushNotification(user, app_id, u2f_device, super_gluu_request) - send_ios += device_push_result["send_ios"] - send_android += device_push_result["send_android"] - else: - print "Super-Gluu-Push. No device enrolled for user '%s'" % user_name - return 0 - - msg = """Super-Gluu-Push. Send push notification. send_android: '%s', send_ios: '%s' """ - print msg % (send_android, send_ios) - return send_android + send_ios - - - - - - - def sendDevicePushNotification(self, user, app_id, u2f_device, super_gluu_request): - - device_data = u2f_device.getDeviceData() - if device_data == None: - return {"send_android":0,"send_ios":0} - - platform = device_data.getPlatform() - push_token = device_data.getPushToken() - pushNotificationContext = PushNotificationContext(app_id,super_gluu_request) - pushNotificationContext.debugEnabled = self.debugEnabled - pushNotificationContext.user = user - pushNotificationContext.u2fDevice = u2f_device - pushNotificationContext.devicePlatform = platform - pushNotificationContext.pushToken = push_token - send_ios = 0 - send_android = 0 - - if StringHelper.equalsIgnoreCase(platform,"ios") and StringHelper.isNotEmpty(push_token): - # Sending notification to iOS user's device - if self.pushAppleService == None: - print "Super-Gluu-Push. Apple push notification service disabled" - else: - self.sendApplePushNotification(pushNotificationContext) - send_ios = 1 - - if StringHelper.equalsIgnoreCase(platform,"android") and StringHelper.isNotEmpty(push_token): - # Sending notification to android user's device - if self.pushAndroidService == None: - print "Super-Gluu-Push. Android push notification service disabled" - else: - self.sendAndroidPushNotification(pushNotificationContext) - send_android = 1 - - - return {"send_android":send_android,"send_ios":send_ios} - - - - - def sendApplePushNotification(self, pushNotificationContext): - - if self.pushSnsMode or self.pushGluuMode: - if self.pushSnsMode: - self.sendApplePushSnsNotification(pushNotificationContext) - elif self.pushGluuMode: - self.sendApplePushGluuNotification(pushNotificationContext) - else: - self.sendApplePushNativeNotification(pushNotificationContext) - - def sendAndroidPushNotification(self, pushNotificationContext): - - if self.pushSnsMode or self.pushGluuMode: - if self.pushSnsMode: - self.sendAndroidPushSnsNotification(pushNotificationContext) - elif self.pushGluuMode: - self.sendAndroidPushGluuNotification(pushNotificationContext) - else: - self.sendAndroidPushNativeNotification(pushNotificationContext) - - - - def sendApplePushSnsNotification(self, pushNotificationContext): - - debug = pushNotificationContext.debugEnabled - apple_push_platform = PushPlatform.APNS - targetEndpointArn = self.getTargetEndpointArn(apple_push_platform,pushNotificationContext) - if targetEndpointArn == None: - return None - - push_message = self.buildApplePushMessage(pushNotificationContext) - apple_push_platform = PushPlatform.APNS - if not self.pushAppleServiceProduction: - apple_push_platform = PushPlatform.APNS_SANDBOX - - pushSnsService = pushNotificationContext.pushSnsService - send_notification_result = pushSnsService.sendPushMessage(self.pushAppleService, apple_push_platform, targetEndpointArn, push_message, None) - if debug: - dbg_msg = """Super-Gluu-Push. Send iOS SNS push notification. - message: '%s', send_notification_result: '%s'""" - print dbg_msg % (push_message, send_notification_result) - - def sendAndroidPushSnsNotification(self, pushNotificationContext): - - debug = pushNotificationContext.debugEnabled - android_push_platform = PushPlatform.GCM - targetEndpointArn = self.getTargetEndpointArn(android_push_platform, pushNotificationContext) - if targetEndpointArn == None: - return None - pushSnsService = pushNotificationContext.pushSnsService - push_message = self.buildAndroidPushMessage(pushNotificationContext) - send_notification_result = pushSnsService.sendPushMessage(self.pushAndroidService, android_push_platform, targetEndpointArn, push_message, None) - if debug: - dbg_msg = """Super-Gluu-Push. Send Android SNS push notification. - message:'%s', send_notification_result: '%s'""" - print dbg_msg % (push_message, send_notification_result) - - - - def sendApplePushGluuNotification(self, pushNotificationContext): - - debug = pushNotificationContext.debugEnabled - apple_push_platform = PushPlatform.APNS - targetEndpointArn = self.getTargetEndpointArn(apple_push_platform, pushNotificationContext) - if targetEndpointArn == None: - return None - - push_message = self.buildApplePushMessage(pushNotificationContext) - print "push message : %s" % push_message - send_notification_result = self.pushAppleService.sendNotification(self.pushAppleServiceAuth, targetEndpointArn, push_message) - print "push message sent" - if debug: - dbg_msg = """Super-Gluu-Push. Send iOS gluu push notification. - message: '%s', send_notification_result: '%s'""" - print dbg_msg % (push_message, send_notification_result) - - def sendAndroidPushGluuNotification(self, pushNotificationContext): - - debug = pushNotificationContext.debugEnabled - android_push_platform = PushPlatform.GCM - targetEndpointArn = self.getTargetEndpointArn(android_push_platform, pushNotificationContext) - if targetEndpointArn == None: - return None - push_message = self.buildAndroidPushMessage(pushNotificationContext) - send_notification_result = self.pushAndroidService.sendNotification(self.pushAndroidServiceAuth, targetEndpointArn, push_message) - if debug: - dbg_msg = """Super-Gluu-Push. Send Android gluu push notification. - message: '%s', send_notification_result: '%s' """ - print dbg_msg % (push_message,send_notification_result) - pass - - def sendApplePushNativeNotification(self, pushNotificationContext): - - title = self.titleTemplate - message = self.messageTemplate % pushNotificationContext.appId - push_token = pushNotificationContext.pushToken - additional_fields = {"request": pushNotificationContext.superGluuRequest} - debug = pushNotificationContext.debugEnabled - msgBuilder = APNS.newPayload().alertBody(message).alertTitle(title).sound("default") - msgBuilder.forNewsstand() - msgBuilder.customFields(additional_fields) - push_message = msgBuilder.build() - send_notification_result = self.pushAppleService.push(push_token, push_message) - if debug: - dbg_msg = """Super-Gluu-Push. Send iOS native push notification. - push_token:'%s', message: '%s', send_notification_result: '%s'""" - print dbg_msg % (push_token, push_message, send_notification_result) - - - def sendAndroidPushNativeNotification(self, pushNotificationContext): - title = self.titleTemplate - superGluuRequest = pushNotificationContext.superGluuRequest - msgBuilder = Message.Builder().addData("message", superGluuRequest).addData("title",title).collapseKey("single").contentAvailable(True) - push_message = msgBuilder.build() - push_token = pushNotificationContext.pushToken - send_notification_result = self.pushAndroidService.send(push_message, push_token, 3) - if pushNotificationContext.debugEnabled: - dbg_msg = """Super-Gluu-Push. Send iOS native push notification. - push_token:'%s', message: '%s', send_notification_result: '%s'""" - print dbg_msg % (push_token, push_message, send_notification_result) - - - - def buildApplePushMessage(self, pushNotificationContext): - - title = self.titleTemplate - message = self.messageTemplate % pushNotificationContext.appId - sns_push_request_dictionary = { - "request": pushNotificationContext.superGluuRequest, - "aps": { - "badge": 0, - "alert": {"body":message,"title":title}, - "category": "ACTIONABLE", - "content-available": "1", - "sound": "default" - } - } - return json.dumps(sns_push_request_dictionary,separators=(',',':')) - - def buildAndroidPushMessage(self, pushNotificationContext): - - sns_push_request_dictionary = { - "collapse_key": "single", - "content_available": True, - "time_to_live": 60, - "data": { - "message": pushNotificationContext.superGluuRequest, - "title": self.titleTemplate - } - } - return json.dumps(sns_push_request_dictionary,separators=(',',':')) - - def getTargetEndpointArn(self, platform, pushNotificationContext): - - deviceRegistrationService = pushNotificationContext.deviceRegistrationService - pushSnsService = pushNotificationContext.pushSnsService - user = pushNotificationContext.user - u2fDevice = pushNotificationContext.u2fDevice - targetEndpointArn = None - - # Return endpoint ARN if it is created already - notificationConf = u2fDevice.getDeviceNotificationConf() - if StringHelper.isNotEmpty(notificationConf): - notificationConfJson = json.loads(notificationConf) - targetEndpointArn = notificationConfJson['sns_endpoint_arn'] - if StringHelper.isNotEmpty(targetEndpointArn): - print "Super-Gluu-Push. Target endpoint ARN already created : ", targetEndpointArn - return targetEndpointArn - - # Create endpoint ARN - pushClient = None - pushClientAuth = None - platformApplicationArn = None - if platform == PushPlatform.GCM: - pushClient = self.pushAndroidService - if self.pushSnsMode: - platformApplicationArn = self.pushAndroidPlatformArn - if self.pushGluuMode: - pushClientAuth = self.pushAndroidServiceAuth - elif platform == PushPlatform.APNS: - pushClient = self.pushAppleService - if self.pushSnsMode: - platformApplicationArn = self.pushApplePlatformArn - if self.pushGluuMode: - pushClientAuth = self.pushAppleServiceAuth - else: - print "Super-Gluu-Push. Unsupported platform for ARN." - return None - - deviceData = u2fDevice.getDeviceData() - pushToken = deviceData.getPushToken() - - print "Super-Gluu-Push. Attempting to create target endpoint ARN for user: %s" % user.getUserId() - if self.pushSnsMode: - targetEndpointArn = pushSnsService.createPlatformArn(pushClient,platformApplicationArn,pushToken,user) - else: - customUserData = pushSnsService.getCustomUserData(user) - registerDeviceResponse = pushClient.registerDevice(pushClientAuth, pushToken, customUserData) - if registerDeviceResponse != None and registerDeviceResponse.getStatusCode() == 200: - targetEndpointArn = registerDeviceResponse.getEndpointArn() - - if StringHelper.isEmpty(targetEndpointArn): - print "Super-Gluu-Push. Failed to get endpoint ARN for user: '%s'" % user.getUserId() - return None - - printmsg = "Super-Gluu-Push. Create target endpoint ARN '%s' for user '%s'" - print printmsg % (targetEndpointArn, user.getUserId()) - - # Store created endpoint ARN in device entry - userInum = user.getAttribute("inum") - u2fDeviceUpdate = deviceRegistrationService.findUserDeviceRegistration(userInum, u2fDevice.getId()) - u2fDeviceUpdate.setDeviceNotificationConf('{"sns_endpoint_arn": "%s"}' % targetEndpointArn) - deviceRegistrationService.updateDeviceRegistration(userInum,u2fDeviceUpdate) - - return targetEndpointArn - - -# -# GeolocationData Class -# -class GeolocationData: - def __init__(self,response): - self.city = response['city'] - self.country = response['country'] - self.region = response['regionName'] - - -# -# Network Api -# -class NetworkApi: - def __init__(self, conn_timeout =15 * 1000): - self.conn_timeout = conn_timeout - print "NetApi. {conn_timeout=%d}" % conn_timeout - - def get_remote_ip_from_request(self, servletRequest): - try: - remote_ip = servletRequest.getHeader("X-FORWARDED-FOR") - if StringHelper.isEmpty(remote_ip): - remote_ip = servletRequest.getRemoteAddr() - - return remote_ip - except: - print "NetApi. Could not determine remote location: ", sys.exc_info()[1] - - return None - - - def get_geolocation_data(self, remote_ip): - print "NetApi. Determining remote location for ip address '%s'" % remote_ip - httpService = CdiUtil.bean(HttpService) - - http_client = httpService.getHttpsClient() - http_client_params = http_client.getParams() - http_client_params.setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT,self.conn_timeout) - - geolocation_service_url = "http://ip-api.com/json/%s?fields=49177" % remote_ip - geolocation_service_headers = { "Accept": "application/json"} - - try: - http_service_response = httpService.executeGet(http_client,geolocation_service_url,geolocation_service_headers) - http_response = http_service_response.getHttpResponse() - except: - print "NetApi. Could not determine remote location: ", sys.exc_info()[1] - return None - - try: - if not httpService.isResponseStastusCodeOk(http_response): - http_error_str = str(http_response.getStatusLine().getStatusCode()) - print "NetApi. Could not determine remote location: ",http_error_str - httpService.consume(http_response) - return None - - response_bytes = httpService.getResponseContent(http_response) - response_string = httpService.convertEntityToString(response_bytes) - httpService.consume(http_response) - finally: - http_service_response.closeConnection() - - if response_string == None: - print "NetApi. Could not determine remote location. Empty respone from server" - return None - - response = json.loads(response_string) - - if not StringHelper.equalsIgnoreCase(response['status'],"success"): - print "NetApi. Could not determine remote location. ip-api status: '%s'" % response['status'] - return None - - return GeolocationData(response) - - -# -# SuperGluuRequestBuilder class -# - -class SuperGluuRequestBuilder: - def __init__(self, method="authenticate"): - self.username = '' - self.app = '' - self.issuer = '' - self.state = '' - self.method = method - self.licensed = False - self.created = DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(ZonedDateTime.now().withNano(0)) - self.req_ip = '' - self.req_loc = '' - - def is_authenticate_method(self): - self.method = "authenticate" - - def is_enroll_method(self): - self.method = "enroll" - - def requestLocation(self, geoloc_data): - if geoloc_data != None: - self.req_loc = "%s, %s, %s" % (geoloc_data.country, geoloc_data.city, geoloc_data.region) - else: - self.req_loc = "" - - def build(self): - request_dict = { - "username" : self.username, - "app" : self.app, - "issuer" : self.issuer, - "state": self.state, - "method": self.method, - "licensed" : self.licensed, - "created" : self.created, - "req_ip" : self.req_ip, - "req_loc" : self.req_loc - } - - return json.dumps(request_dict,separators=(',',':')) - diff --git a/docker-jans-auth-server/libs/python.txt b/docker-jans-auth-server/libs/python.txt deleted file mode 100644 index 15b040bf68e..00000000000 --- a/docker-jans-auth-server/libs/python.txt +++ /dev/null @@ -1 +0,0 @@ -Put python modules needed in custom scripts into this folder. \ No newline at end of file diff --git a/docker-jans-auth-server/scripts/entrypoint.sh b/docker-jans-auth-server/scripts/entrypoint.sh index 397e37b3f28..aa62ccaebc5 100644 --- a/docker-jans-auth-server/scripts/entrypoint.sh +++ b/docker-jans-auth-server/scripts/entrypoint.sh @@ -18,15 +18,18 @@ get_debug_opt() { move_builtin_jars() { # move twilio lib if [ ! -f /opt/jans/jetty/jans-auth/custom/libs/twilio.jar ]; then - # mv /usr/share/java/twilio.jar /opt/jans/jetty/jans-auth/custom/libs/twilio.jar cp /usr/share/java/twilio.jar /opt/jans/jetty/jans-auth/custom/libs/twilio.jar fi # move jsmpp lib if [ ! -f /opt/jans/jetty/jans-auth/custom/libs/jsmpp.jar ]; then - # mv /usr/share/java/jsmpp.jar /opt/jans/jetty/jans-auth/custom/libs/jsmpp.jar cp /usr/share/java/jsmpp.jar /opt/jans/jetty/jans-auth/custom/libs/jsmpp.jar fi + + # move casa-config lib + if [ ! -f /opt/jans/jetty/jans-auth/custom/libs/casa-config.jar ]; then + cp /usr/share/java/casa-config.jar /opt/jans/jetty/jans-auth/custom/libs/casa-config.jar + fi } # ========== diff --git a/docker-jans-configurator/README.md b/docker-jans-configurator/README.md index 10e3d9574e2..57e236de557 100644 --- a/docker-jans-configurator/README.md +++ b/docker-jans-configurator/README.md @@ -82,7 +82,7 @@ The load command can be used either to generate or restore config and secret for - `auth_sig_keys`: space-separated key algorithm for signing (default to `RS256 RS384 RS512 ES256 ES384 ES512 PS256 PS384 PS512`) - `auth_enc_keys`: space-separated key algorithm for encryption (default to `RSA1_5 RSA-OAEP`) - - `optional_scopes`: list of scopes that will be used (supported scopes are `ldap`, `scim`, `fido2`, `client-api`, `couchbase`, `redis`, `sql`; default to empty list) + - `optional_scopes`: list of scopes that will be used (supported scopes are `ldap`, `scim`, `fido2`, `client-api`, `couchbase`, `redis`, `sql`, `casa`; default to empty list) - `ldap_pw`: user's password to access LDAP database (only used if `optional_scopes` list contains `ldap` scope) - `sql_pw`: user's password to access SQL database (only used if `optional_scopes` list contains `sql` scope) - `couchbase_pw`: user's password to access Couchbase database (only used if `optional_scopes` list contains `couchbase` scope) diff --git a/docker-jans-configurator/scripts/bootstrap.py b/docker-jans-configurator/scripts/bootstrap.py index cfda0f3859d..cf1e037fe50 100644 --- a/docker-jans-configurator/scripts/bootstrap.py +++ b/docker-jans-configurator/scripts/bootstrap.py @@ -762,6 +762,14 @@ def fido2_ctx(self): def sql_ctx(self): self.set_secret("sql_password", self.params["sql_pw"]) + def casa_ctx(self): + self.set_config("casa_client_id", lambda: f"1902.{uuid4()}") + casa_client_pw = self.set_secret("casa_client_pw", get_random_chars) + self.set_secret( + "casa_client_encoded_pw", + partial(encode_text, casa_client_pw, self.get_secret("encoded_salt")) + ) + def generate(self): opt_scopes = self.params["optional_scopes"] @@ -796,6 +804,9 @@ def generate(self): if "sql" in opt_scopes: self.sql_ctx() + if "casa" in opt_scopes: + self.casa_ctx() + # populated config return self.ctx diff --git a/docker-jans-configurator/scripts/parameter.py b/docker-jans-configurator/scripts/parameter.py index 345c831ca62..9c6bb03b46b 100644 --- a/docker-jans-configurator/scripts/parameter.py +++ b/docker-jans-configurator/scripts/parameter.py @@ -31,6 +31,7 @@ "couchbase", "redis", "sql", + "casa", ) diff --git a/docker-jans-persistence-loader/scripts/couchbase_setup.py b/docker-jans-persistence-loader/scripts/couchbase_setup.py index c6c27f7b532..1dd4d6ac6d9 100644 --- a/docker-jans-persistence-loader/scripts/couchbase_setup.py +++ b/docker-jans-persistence-loader/scripts/couchbase_setup.py @@ -17,6 +17,7 @@ from utils import prepare_template_ctx from utils import render_ldif from utils import get_ldif_mappings +from utils import id_from_dn logging.config.dictConfig(LOGGING_CONFIG) logger = logging.getLogger("entrypoint") @@ -73,16 +74,6 @@ def get_bucket_mappings(manager): return bucket_mappings -def get_key_from(dn): - # for example: `"inum=29DA,ou=attributes,o=jans"` - # becomes `["29DA", "attributes"]` - dns = [i.split("=")[-1] for i in dn.split(",") if i != "o=jans"] - dns.reverse() - - # the actual key - return '_'.join(dns) or "_" - - class AttrProcessor(object): def __init__(self): self._attrs = {} @@ -355,7 +346,7 @@ def import_ldif(self, bucket_mappings): if len(entry) <= 2: continue - key = get_key_from(dn) + key = id_from_dn(dn) entry["dn"] = [dn] entry = transform_entry(entry, attr_processor) data = json.dumps(entry) diff --git a/docker-jans-persistence-loader/scripts/spanner_setup.py b/docker-jans-persistence-loader/scripts/spanner_setup.py index b770973521b..72c51f300a3 100644 --- a/docker-jans-persistence-loader/scripts/spanner_setup.py +++ b/docker-jans-persistence-loader/scripts/spanner_setup.py @@ -7,7 +7,6 @@ from collections import OrderedDict from collections import defaultdict -from ldap3.utils import dn as dnutils from ldif import LDIFParser from jans.pycloudlib.persistence.spanner import SpannerClient @@ -16,6 +15,7 @@ from utils import prepare_template_ctx from utils import render_ldif from utils import get_ldif_mappings +from utils import doc_id_from_dn FIELD_RE = re.compile(r"[^0-9a-zA-Z\s]+") @@ -318,9 +318,7 @@ def data_from_ldif(self, filename): parser = LDIFParser(fd) for dn, entry in parser.parse(): - parsed_dn = dnutils.parse_dn(dn) - # rdn_name = parsed_dn[0][0] - doc_id = parsed_dn[0][1] + doc_id = doc_id_from_dn(dn) oc = entry.get("objectClass") or entry.get("objectclass") if oc: diff --git a/docker-jans-persistence-loader/scripts/sql_setup.py b/docker-jans-persistence-loader/scripts/sql_setup.py index 79e72b159af..a8dff051c89 100644 --- a/docker-jans-persistence-loader/scripts/sql_setup.py +++ b/docker-jans-persistence-loader/scripts/sql_setup.py @@ -7,7 +7,6 @@ from collections import defaultdict from string import Template -from ldap3.utils import dn as dnutils from ldif import LDIFParser from jans.pycloudlib.persistence.sql import SQLClient @@ -16,6 +15,7 @@ from utils import prepare_template_ctx from utils import render_ldif from utils import get_ldif_mappings +from utils import doc_id_from_dn FIELD_RE = re.compile(r"[^0-9a-zA-Z\s]+") @@ -324,9 +324,7 @@ def data_from_ldif(self, filename): parser = LDIFParser(fd) for dn, entry in parser.parse(): - parsed_dn = dnutils.parse_dn(dn) - # rdn_name = parsed_dn[0][0] - doc_id = parsed_dn[0][1] + doc_id = doc_id_from_dn(dn) oc = entry.get("objectClass") or entry.get("objectclass") if oc: diff --git a/docker-jans-persistence-loader/scripts/upgrade.py b/docker-jans-persistence-loader/scripts/upgrade.py index 4ffc8dde9a2..a5554e6734c 100644 --- a/docker-jans-persistence-loader/scripts/upgrade.py +++ b/docker-jans-persistence-loader/scripts/upgrade.py @@ -4,8 +4,6 @@ import os from collections import namedtuple -from ldap3.utils import dn as dnutils - from jans.pycloudlib.persistence.couchbase import get_couchbase_user from jans.pycloudlib.persistence.couchbase import get_couchbase_superuser from jans.pycloudlib.persistence.couchbase import get_couchbase_password @@ -16,6 +14,8 @@ from jans.pycloudlib.persistence.sql import SQLClient from settings import LOGGING_CONFIG +from utils import doc_id_from_dn +from utils import id_from_dn logging.config.dictConfig(LOGGING_CONFIG) logger = logging.getLogger("entrypoint") @@ -23,25 +23,6 @@ Entry = namedtuple("Entry", ["id", "attrs"]) -def doc_id_from_dn(dn): - parsed_dn = dnutils.parse_dn(dn) - doc_id = parsed_dn[0][1] - - if doc_id == "jans": - doc_id = "_" - return doc_id - - -def id_from_dn(dn): - # for example: `"inum=29DA,ou=attributes,o=jans"` - # becomes `["29DA", "attributes"]` - dns = [i.split("=")[-1] for i in dn.split(",") if i != "o=jans"] - dns.reverse() - - # the actual key - return '_'.join(dns) or "_" - - class BaseBackend: """Base class for backend adapters. Must be sub-classed per implementation details. @@ -62,6 +43,13 @@ def __init__(self): ] +#: ID of base entry +JANS_BASE_ID = "o=jans" + +#: ID of manager group +JANS_MANAGER_GROUP = "inum=60B7,ou=groups,o=jans" + + class LDAPBackend(BaseBackend): def __init__(self, manager): super().__init__() @@ -168,6 +156,16 @@ def update_scim_scopes_entries(self): "jansAttrs"] = self.jans_attrs self.modify_entry(id_, entry.attrs, **kwargs) + def update_base_entries(self): + # add jansManagerGrp to base entry + entry = self.get_entry(JANS_BASE_ID) + if not entry: + return + + if not entry.attrs.get("jansManagerGrp"): + entry.attrs["jansManagerGrp"] = JANS_MANAGER_GROUP + self.modify_entry(JANS_BASE_ID, entry.attrs) + class SQLBackend(BaseBackend): def __init__(self, manager): @@ -261,6 +259,19 @@ def update_scim_scopes_entries(self): "jansAttrs"] = self.jans_attrs self.modify_entry(id_, entry.attrs, **kwargs) + def update_base_entries(self): + # add jansManagerGrp to base entry + id_ = doc_id_from_dn(JANS_BASE_ID) + kwargs = {"table_name": "jansOrganization"} + + entry = self.get_entry(id_, **kwargs) + if not entry: + return + + if not entry.attrs.get("jansManagerGrp"): + entry.attrs["jansManagerGrp"] = JANS_MANAGER_GROUP + self.modify_entry(id_, entry.attrs, **kwargs) + class CouchbaseBackend(BaseBackend): def __init__(self, manager): @@ -414,6 +425,20 @@ def update_misc(self): # drop the index self.client.exec_query(f'DROP INDEX `{bucket}`.`def_jans_fix_oc`') + def update_base_entries(self): + # add jansManagerGrp to base entry + id_ = id_from_dn(JANS_BASE_ID) + bucket = os.environ.get("CN_COUCHBASE_BUCKET_PREFIX", "jans") + kwargs = {"bucket": bucket} + + entry = self.get_entry(id_, **kwargs) + if not entry: + return + + if not entry.attrs.get("jansManagerGrp"): + entry.attrs["jansManagerGrp"] = JANS_MANAGER_GROUP + self.modify_entry(id_, entry.attrs, **kwargs) + class SpannerBackend(BaseBackend): def __init__(self, manager): @@ -507,6 +532,19 @@ def update_scim_scopes_entries(self): "jansAttrs"] = self.jans_attrs self.modify_entry(id_, entry.attrs, **kwargs) + def update_base_entries(self): + # add jansManagerGrp to base entry + id_ = doc_id_from_dn(JANS_BASE_ID) + kwargs = {"table_name": "jansOrganization"} + + entry = self.get_entry(id_, **kwargs) + if not entry: + return + + if not entry.attrs.get("jansManagerGrp"): + entry.attrs["jansManagerGrp"] = JANS_MANAGER_GROUP + self.modify_entry(id_, entry.attrs, **kwargs) + class Upgrade: def __init__(self, manager): @@ -529,6 +567,7 @@ def invoke(self): self.backend.update_scopes_entries() self.backend.update_clients_entries() self.backend.update_scim_scopes_entries() + self.backend.update_base_entries() if hasattr(self.backend, "update_misc"): self.backend.update_misc() diff --git a/docker-jans-persistence-loader/scripts/utils.py b/docker-jans-persistence-loader/scripts/utils.py index 78f037f74ec..e6faa0cbd47 100644 --- a/docker-jans-persistence-loader/scripts/utils.py +++ b/docker-jans-persistence-loader/scripts/utils.py @@ -5,6 +5,8 @@ from urllib.parse import urlparse from uuid import uuid4 +from ldap3.utils import dn as dnutils + from jans.pycloudlib.utils import as_boolean from jans.pycloudlib.utils import encode_text from jans.pycloudlib.utils import safe_render @@ -157,8 +159,8 @@ def get_base_ctx(manager): "scim_client_id": manager.config.get("scim_client_id"), "scim_client_encoded_pw": manager.secret.get("scim_client_encoded_pw"), "casa_enable_script": str(as_boolean(casa_enabled)).lower(), - "oxd_hostname": "localhost", - "oxd_port": "8443", + # "oxd_hostname": "localhost", + # "oxd_port": "8443", "jca_client_id": manager.config.get("jca_client_id"), "jca_client_encoded_pw": manager.secret.get("jca_client_encoded_pw"), } @@ -372,7 +374,31 @@ def get_injected_urls(): return ctx +def merge_casa_ctx(manager, ctx): + # Casa client + ctx["casa_client_id"] = manager.config.get("casa_client_id") + if not ctx["casa_client_id"]: + ctx["casa_client_id"] = f"1902.{uuid4()}" + manager.config.set("casa_client_id", ctx["casa_client_id"]) + + ctx["casa_client_pw"] = manager.secret.get("casa_client_pw") + if not ctx["casa_client_pw"]: + ctx["casa_client_pw"] = get_random_chars() + manager.secret.set("casa_client_pw", ctx["casa_client_pw"]) + + ctx["casa_client_encoded_pw"] = manager.secret.get("casa_client_encoded_pw") + if not ctx["casa_client_encoded_pw"]: + ctx["casa_client_encoded_pw"] = encode_text( + ctx["casa_client_pw"], manager.secret.get("encoded_salt"), + ).decode() + manager.secret.set("casa_client_encoded_pw", ctx["casa_client_encoded_pw"]) + + return ctx + + def prepare_template_ctx(manager): + opt_scopes = json.loads(manager.config.get("optional_scopes", "[]")) + ctx = get_base_ctx(manager) ctx = merge_extension_ctx(ctx) # ctx = merge_radius_ctx(ctx) @@ -383,6 +409,9 @@ def prepare_template_ctx(manager): # ctx = merge_passport_ctx(ctx) ctx = merge_fido2_ctx(ctx) ctx = merge_scim_ctx(ctx) + + if "casa" in opt_scopes: + ctx = merge_casa_ctx(manager, ctx) return ctx @@ -432,6 +461,12 @@ def default_files(): files += [ "jans-fido2/configuration.ldif", ] + + if "casa" in optional_scopes: + files += [ + "gluu-casa/configuration.ldif", + "gluu-casa/clients.ldif", + ] return files def user_files(): @@ -462,3 +497,22 @@ def site_files(): "session": [], } return ldif_mappings + + +def doc_id_from_dn(dn): + parsed_dn = dnutils.parse_dn(dn) + doc_id = parsed_dn[0][1] + + if doc_id == "jans": + doc_id = "_" + return doc_id + + +def id_from_dn(dn): + # for example: `"inum=29DA,ou=attributes,o=jans"` + # becomes `["29DA", "attributes"]` + dns = [i.split("=")[-1] for i in dn.split(",") if i != "o=jans"] + dns.reverse() + + # the actual key + return '_'.join(dns) or "_" diff --git a/docker-jans-persistence-loader/static/extension/person_authentication/Casa.py b/docker-jans-persistence-loader/static/extension/person_authentication/Casa.py index 6ff7d03d29e..55f779bf6dc 100644 --- a/docker-jans-persistence-loader/static/extension/person_authentication/Casa.py +++ b/docker-jans-persistence-loader/static/extension/person_authentication/Casa.py @@ -1,26 +1,25 @@ -# Author: Jose Gonzalez - -from java.util import Collections, HashMap, HashSet, ArrayList, Arrays, Date -from java.nio.charset import Charset - -from org.apache.http.params import CoreConnectionPNames - -from org.oxauth.persistence.model.configuration import JanssenConfiguration +from io.jans.as.persistence.model.configuration import GluuConfiguration from io.jans.as.server.security import Identity from io.jans.as.server.service import AuthenticationService, UserService -from io.jans.as.service.common import EncryptionService -from io.jans.as.service.custom import CustomScriptService -from io.jans.as.service.net import HttpService -from io.jans.as.util import ServerUtil +from io.jans.as.common.service.common import EncryptionService +from io.jans.as.server.service.custom import CustomScriptService +from io.jans.as.server.service.net import HttpService +from io.jans.as.server.util import ServerUtil from io.jans.model import SimpleCustomProperty -from io.jans.model.casa import ApplicationConfiguration from io.jans.model.custom.script import CustomScriptType from io.jans.model.custom.script.type.auth import PersonAuthenticationType -from io.jans.persist import PersistenceEntryManager +from io.jans.orm import PersistenceEntryManager from io.jans.service import CacheService from io.jans.service.cdi.util import CdiUtil from io.jans.util import StringHelper +from java.lang import Integer +from java.util import Collections, HashMap, HashSet, ArrayList, Arrays, Date +from java.nio.charset import Charset + +from org.gluu.casa.model import ApplicationConfiguration +from org.apache.http.params import CoreConnectionPNames + try: import json except ImportError: @@ -31,7 +30,7 @@ class PersonAuthentication(PersonAuthenticationType): def __init__(self, currentTimeMillis): self.currentTimeMillis = currentTimeMillis - self.ACR_SG = "super.jans. + self.ACR_SG = "super_gluu" self.ACR_U2F = "u2f" self.modulePrefix = "casa-external_" @@ -43,7 +42,7 @@ def init(self, customScript, configurationAttributes): self.uid_attr = self.getLocalPrimaryKey() custScriptService = CdiUtil.bean(CustomScriptService) - self.scriptsList = custScriptService.findCustomScripts(Collections.singletonList(CustomScriptType.PERSON_AUTHENTICATION), "oxConfigurationProperty", "displayName", "jansEnabled", "oxLevel") + self.scriptsList = custScriptService.findCustomScripts(Collections.singletonList(CustomScriptType.PERSON_AUTHENTICATION), "jansConfProperty", "displayName", "jansEnabled", "jansLevel") dynamicMethods = self.computeMethods(self.scriptsList) if len(dynamicMethods) > 0: @@ -62,7 +61,7 @@ def init(self, customScript, configurationAttributes): u2f_application_id = configurationAttributes.get("u2f_app_id").getValue2() configAttrs.put("u2f_application_id", SimpleCustomProperty("u2f_application_id", u2f_application_id)) elif acr == self.ACR_SG: - application_id = configurationAttributes.get("supe.jans.app_id").getValue2() + application_id = configurationAttributes.get("supergluu_app_id").getValue2() configAttrs.put("application_id", SimpleCustomProperty("application_id", application_id)) if module.init(None, configAttrs): @@ -123,13 +122,14 @@ def authenticate(self, configurationAttributes, requestParameters, step): print "Casa. authenticate for step 1. Unknown username" else: platform_data = self.parsePlatformData(requestParameters) - mfaOff = foundUser.getAttribute("oxPreferredMethod") == None + preferred = foundUser.getAttribute("jansPreferredMethod") + mfaOff = preferred == None logged_in = False if mfaOff: logged_in = authenticationService.authenticate(user_name, user_password) else: - acr = self.getSuitableAcr(foundUser, platform_data) + acr = self.getSuitableAcr(foundUser, platform_data, preferred) if acr != None: module = self.authenticators[acr] logged_in = module.authenticate(module.configAttrs, requestParameters, step) @@ -184,7 +184,7 @@ def authenticate(self, configurationAttributes, requestParameters, step): if tdi == None: print "Casa. authenticate. List of user's trusted devices was not updated" else: - user.setAttribute("oxTrustedDevicesInfo", tdi) + user.setAttribute("jansTrustedDevices", tdi) userService.updateUser(user) else: print "Casa. authenticate. 2FA authentication failed" @@ -296,10 +296,10 @@ def logout(self, configurationAttributes, requestParameters): def getLocalPrimaryKey(self): entryManager = CdiUtil.bean(PersistenceEntryManager) - config = JanssenConfiguration() - config = entryManager.find(config.getClass(), "ou=configuration,o.jans.) + config = GluuConfiguration() + config = entryManager.find(config.getClass(), "ou=configuration,o=jans") #Pick (one) attribute where user id is stored (e.g. uid/mail) - uid_attr = config.getOxIDPAuthentication().get(0).getConfig().getPrimaryKey() + uid_attr = config.getIdpAuthn().get(0).getConfig().findValue("primaryKey").asText() print "Casa. init. uid attribute is '%s'" % uid_attr return uid_attr @@ -307,11 +307,10 @@ def getLocalPrimaryKey(self): def getSettings(self): entryManager = CdiUtil.bean(PersistenceEntryManager) config = ApplicationConfiguration() - config = entryManager.find(config.getClass(), "ou=casa,ou=configuration,o.jans.) - settings = None - try: - settings = json.loads(config.getSettings()) - except: + config = entryManager.find(config.getClass(), "ou=casa,ou=configuration,o=jans") + settings = config.getSettings() + + if settings == None: print "Casa. getSettings. Failed to parse casa settings from DB" return settings @@ -322,8 +321,8 @@ def computeMethods(self, scriptList): mapping = {} cmConfigs = self.getSettings() - if cmConfigs != None and 'acr_plugin_mapping' in cmConfigs: - mapping = cmConfigs['acr_plugin_mapping'] + if cmConfigs != None: + mapping = cmConfigs.getAcrPluginMap().keySet() for m in mapping: for customScript in scriptList: @@ -371,34 +370,34 @@ def getAvailMethodsUser(self, user, skip=None): def prepareUIParams(self, identity): - + print "Casa. prepareUIParams. Reading UI branding params" cacheService = CdiUtil.bean(CacheService) casaAssets = cacheService.get("casa_assets") - + if casaAssets == None: - #This may happen when cache type is IN_MEMORY, where actual cache is merely a local variable + #This may happen when cache type is IN_MEMORY, where actual cache is merely a local variable #(a expiring map) living inside Casa webapp, not oxAuth webapp - + sets = self.getSettings() - + custPrefix = "/custom" logoUrl = "/images/logo.png" faviconUrl = "/images/favicon.ico" - if ("extra_css" in sets and sets["extra_css"] != None) or sets["use_branding"]: + if (sets.getExtraCssSnippet() != None) or sets.isUseExternalBranding(): logoUrl = custPrefix + logoUrl faviconUrl = custPrefix + faviconUrl - - prefix = custPrefix if sets["use_branding"] else "" - + + prefix = custPrefix if sets.isUseExternalBranding() else "" + casaAssets = { "contextPath": "/casa", "prefix" : prefix, "faviconUrl" : faviconUrl, - "extraCss": sets["extra_css"] if "extra_css" in sets else None, + "extraCss": sets.getExtraCssSnippet(), "logoUrl": logoUrl } - + #Setting a single variable with the whole map does not work... identity.setWorkingParameter("casa_contextPath", casaAssets['contextPath']) identity.setWorkingParameter("casa_prefix", casaAssets['prefix']) @@ -434,7 +433,7 @@ def parsePlatformData(self, requestParameters): return deviceInf - def getSuitableAcr(self, user, deviceInf): + def getSuitableAcr(self, user, deviceInf, preferred): onMobile = deviceInf != None and 'isMobile' in deviceInf and deviceInf['isMobile'] id = user.getUserId() @@ -444,9 +443,10 @@ def getSuitableAcr(self, user, deviceInf): for s in self.scriptsList: name = s.getName() - if user_methods.contains(name) and name in self.authenticators and s.getLevel() > strongest and (not onMobile or name in self.mobile_methods): + level = Integer.MAX_VALUE if name == preferred else s.getLevel() + if user_methods.contains(name) and level > strongest and (not onMobile or name in self.mobile_methods): acr = name - strongest = s.getLevel() + strongest = level print "Casa. getSuitableAcr. On mobile = %s" % onMobile if acr == None and onMobile: @@ -466,17 +466,11 @@ def determineSkip2FA(self, userService, identity, foundUser, deviceInf): print "Casa. determineSkip2FA. Failed to read policy_2fa" return False - missing = False - if not 'plugins_settings' in cmConfigs: - missing = True - elif not 'strong-authn-settings' in cmConfigs['plugins_settings']: - missing = True - else: - cmConfigs = cmConfigs['plugins_settings']['strong-authn-settings'] + cmConfigs = cmConfigs.getPluginSettings().get('strong-authn-settings') policy2FA = 'EVERY_LOGIN' - if not missing and 'policy_2fa' in cmConfigs: - policy2FA = ','.join(cmConfigs['policy_2fa']) + if cmConfigs != None and cmConfigs.get('policy_2fa') != None: + policy2FA = ','.join(cmConfigs.get('policy_2fa')) print "Casa. determineSkip2FA with general policy %s" % policy2FA policy2FA += ',' @@ -484,7 +478,7 @@ def determineSkip2FA(self, userService, identity, foundUser, deviceInf): if 'CUSTOM,' in policy2FA: #read setting from user profile - policy = foundUser.getAttribute("oxStrongAuthPolicy") + policy = foundUser.getAttribute("jansStrongAuthPolicy") if policy == None: policy = 'EVERY_LOGIN,' else: @@ -510,7 +504,7 @@ def determineSkip2FA(self, userService, identity, foundUser, deviceInf): #Update attribute if authentication will not have second step devInf = identity.getWorkingParameter("trustedDevicesInfo") if devInf != None: - foundUser.setAttribute("oxTrustedDevicesInfo", devInf) + foundUser.setAttribute("jansTrustedDevices", devInf) userService.updateUser(foundUser) else: print "Casa. determineSkip2FA. Unknown %s policy: cannot skip 2FA" % policy @@ -522,7 +516,7 @@ def process2FAPolicy(self, identity, foundUser, deviceInf, locationCriterion, de skip2FA = False #Retrieve user's devices info - devicesInfo = foundUser.getAttribute("oxTrustedDevicesInfo") + devicesInfo = foundUser.getAttribute("jansTrustedDevices") #do geolocation geodata = self.getGeolocation(identity) @@ -661,7 +655,7 @@ def getGeolocation(self, identity): return None - + def getLogoutExternalUrl(self, configurationAttributes, requestParameters): print "Get external logout URL call" return None diff --git a/docker-jans-persistence-loader/templates/base.ldif b/docker-jans-persistence-loader/templates/base.ldif index 106b26fc02a..6a6db142854 100644 --- a/docker-jans-persistence-loader/templates/base.ldif +++ b/docker-jans-persistence-loader/templates/base.ldif @@ -3,6 +3,7 @@ description: Welcome to oxTrust! displayName: %(orgName)s jansOrgShortName: %(orgName)s jansThemeColor: 166309 +jansManagerGrp: inum=60B7,ou=groups,o=jans o: jans objectClass: top objectClass: jansOrganization diff --git a/docker-jans-persistence-loader/templates/gluu-casa/clients.ldif b/docker-jans-persistence-loader/templates/gluu-casa/clients.ldif new file mode 100644 index 00000000000..1d373ba3197 --- /dev/null +++ b/docker-jans-persistence-loader/templates/gluu-casa/clients.ldif @@ -0,0 +1,35 @@ +dn: inum=%(casa_client_id)s,ou=clients,o=jans +objectClass: top +objectClass: jansClnt +del: false +displayName: Casa +inum: %(casa_client_id)s +jansAccessTknAsJwt: false +jansAccessTknSigAlg: RS256 +jansAppTyp: web +jansAttrs: {} +# jansAttrs: {"tlsClientAuthSubjectDn":"","runIntrospectionScriptBeforeAccessTokenAsJwtCreationAndIncludeClaims":false,"keepClientAuthorizationAfterExpiration":false,"allowSpontaneousScopes":false,"spontaneousScopes":[],"spontaneousScopeScriptDns":[],"backchannelLogoutUri":[],"backchannelLogoutSessionRequired":false,"additionalAudience":[],"postAuthnScripts":[],"consentGatheringScripts":[],"introspectionScripts":[],"rptClaimsScripts":[],"parLifetime":600,"requirePar":false,"jansAuthSignedRespAlg":"RS256","jansAuthEncRespAlg":null,"jansAuthEncRespEnc":null} +jansClntSecret: %(casa_client_encoded_pw)s +jansDefAcrValues: simple_password_auth +jansDisabled: false +jansGrantTyp: authorization_code +jansGrantTyp: refresh_token +jansGrantTyp: client_credentials +jansIdTknSignedRespAlg: RS256 +jansInclClaimsInIdTkn: false +jansLogoutSessRequired: false +jansPersistClntAuthzs: true +jansRequireAuthTime: false +jansRespTyp: code +jansRptAsJwt: false +jansPostLogoutRedirectURI: https://%(hostname)s/casa/bye.zul +jansRedirectURI: https://%(hostname)s/casa +jansLogoutURI: https://%(hostname)s/casa/autologout +jansScope: inum=F0C4,ou=scopes,o=jans +jansScope: inum=43F1,ou=scopes,o=jans +jansScope: inum=10B2,ou=scopes,o=jans +jansScope: inum=341A,ou=scopes,o=jans +jansSubjectTyp: pairwise +jansTknEndpointAuthMethod: client_secret_basic +jansTrustedClnt: true +jansSignedRespAlg: RS256 diff --git a/docker-jans-persistence-loader/templates/gluu-casa/configuration.ldif b/docker-jans-persistence-loader/templates/gluu-casa/configuration.ldif new file mode 100644 index 00000000000..c365cf84aa3 --- /dev/null +++ b/docker-jans-persistence-loader/templates/gluu-casa/configuration.ldif @@ -0,0 +1,5 @@ +dn: ou=casa,ou=configuration,o=jans +objectClass: top +objectClass: jansAppConf +ou: casa +jansConfApp: {"enable_pass_reset": true, "oidc_config": {"authz_redirect_uri": "https://%(hostname)s/casa", "post_logout_uri": "https://%(hostname)s/casa/bye.zul", "frontchannel_logout_uri": "https://%(hostname)s/casa/autologout", "scopes": ["openid", "profile", "user_name", "clientinfo"], "op_host": "%(hostname)s", "client": {"clientId": "%(casa_client_id)s", "clientSecret": "%(casa_client_pw)s", "clientName": "Casa"}}, "log_level": "INFO"} diff --git a/docker-jans-persistence-loader/templates/scripts.ldif b/docker-jans-persistence-loader/templates/scripts.ldif index a2d4f106cf7..51271dd3ff7 100644 --- a/docker-jans-persistence-loader/templates/scripts.ldif +++ b/docker-jans-persistence-loader/templates/scripts.ldif @@ -596,3 +596,22 @@ objectClass: top objectClass: jansCustomScr jansEnabled: false jansProgLng: python + +dn: inum=BABA-CACA,ou=scripts,o=jans +objectClass: top +objectClass: jansCustomScr +description: Authentication script for Gluu Casa +displayName: casa +jansEnabled: %(casa_enable_script)s +inum: BABA-CACA +jansConfProperty: {"value1":"supergluu_app_id","value2":"https://%(hostname)s/casa","description":""} +jansConfProperty: {"value1":"u2f_app_id","value2":"https://%(hostname)s","description":""} +# jansConfProperty: {"value1":"mobile_methods","value2":"otp, twilio_sms, super_gluu","description":""} +# jansConfProperty: {"value1":"2fa_requisite","value2":true,"description":""} +jansLevel: 1 +jansModuleProperty: {"value1":"usage_type","value2":"interactive","description":""} +jansModuleProperty: {"value1":"location_type","value2":"ldap","description":""} +jansRevision: 1 +jansScr::%(person_authentication_casa)s +jansScrTyp: person_authentication +jansProgLng: python