def _lookup_provider_policies(provider_type): """ helper, to prevent deleting a provider while it is still used in a policy :param provider_type: the type of provider: sms or email :return: a dictionary with provider names as key and list of policy names """ provider_policies = {} # lookup the policy action name provider_action_name = Policy_action_name.get(provider_type) if not provider_action_name: raise Exception('unknown provider_type for policy lookup! %r' % provider_type) # now have a look at all authentication policies policies = getPolicy({ 'scope': 'authentication', "action": provider_action_name, }) for policy in policies: provider_name = getPolicyActionValue(policies, provider_action_name, is_string=True) if provider_name not in provider_policies: provider_policies[provider_name] = [] provider_policies[provider_name].append(policy) return provider_policies
def is_phone_editable(user=""): """ this function checks the policy scope=selfservice, action=edit_sms This is a int policy, while the '0' is a deny """ # the default string is the OTP value ret = True realm = user.realm login = user.login policies = getPolicy( { "scope": "selfservice", "realm": realm, "action": "edit_sms", "user": login, } ) edit_sms = get_action_value( policies, scope="selfservice", action="edit_sms", default=1 ) if edit_sms == 0: return False return True
def valid_facets(self, realm=None): """ Show the JSON output for the valid facets configured by the enrollment policy 'u2f_valid_facets'. The form of the JSON output is specified by the FIDO Alliance. """ if realm is None: realm = getDefaultRealm() # Get the valid facets as specified in the enrollment policy 'u2f_valid_facets' # for the specific realm get_policy_params = { 'action': 'u2f_valid_facets', 'scope': 'enrollment', 'realm': realm } valid_facets_action_value = getPolicyActionValue( getPolicy(get_policy_params), 'u2f_valid_facets', is_string=True) # the action value contains the semicolon-separated list of valid facets valid_facets = valid_facets_action_value.split(';') # Prepare the response response.content_type = 'application/fido.trusted-apps+json' # as specified by FIDO response_dict = { "trustedFacets": [{ "version": {"major": 1, "minor": 0}, "ids": [] }] } for facet in valid_facets: facet = facet.strip() response_dict['trustedFacets'][0]['ids'].append(facet) return json.dumps(response_dict)
def get_auth_smstext(user="", realm="", context=None): ''' this function checks the policy scope=authentication, action=smstext This is a string policy The function returns the tuple (bool, string), bool: If a policy is defined string: the string to use ''' # the default string is the OTP value ret = False smstext = "<otp>" pol = getPolicy( { 'scope': 'authentication', 'realm': realm, "action": "smstext" }, context=context) if len(pol) > 0: smstext = getPolicyActionValue(pol, "smstext", String=True) log.debug("[get_auth_smstext] got the smstext = %s" % smstext) ret = True return ret, smstext
def is_phone_editable(user=""): ''' this function checks the policy scope=selfservice, action=edit_sms This is a int policy, while the '0' is a deny ''' # the default string is the OTP value ret = True realm = user.realm login = user.login policies = getPolicy({ 'scope': 'selfservice', 'realm': realm, "action": "edit_sms", "user": login }) edit_sms = get_action_value(policies, scope='selfservice', action="edit_sms", default=1) if edit_sms == 0: return False return True
def get_single_auth_policy(policy_name, user=None, realms=None): """ Retrieves a policy value and checks if the value is consistent across realms. :param policy_name: the name of the policy, e.g: * qrtoken_pairing_callback_url * qrtoken_pairing_callback_sms * qrtoken_challenge_response_url * qrtoken_challenge_response_sms :param realms: the realms that his policy should be effective in """ action_values = [] login = None ret = None if user and user.login and user.realm: realms = [user.realm] login = user.login if realms is None or len(realms) == 0: realms = ['/:no realm:/'] params = {"scope": "authentication", 'action': policy_name} for realm in realms: params['realm'] = realm if login: params['user'] = login policy = getPolicy(params) action_value = getPolicyActionValue(policy, policy_name, is_string=True) if action_value: action_values.append(action_value) if len(action_values) > 1: for value in action_values: if value != action_values[0]: raise Exception('conflicting policy values %r found for ' 'realm set: %r' % (action_values, realms)) if action_values: ret = action_values[0] return ret
def valid_facets(self, realm=None): """ Show the JSON output for the valid facets configured by the enrollment policy 'u2f_valid_facets'. The form of the JSON output is specified by the FIDO Alliance. """ if realm is None: realm = getDefaultRealm() # Get the valid facets as specified in the enrollment policy 'u2f_valid_facets' # for the specific realm get_policy_params = { "action": "u2f_valid_facets", "scope": "enrollment", "realm": realm, } valid_facets_action_value = get_action_value( getPolicy(get_policy_params), scope="enrollment", action="u2f_valid_facets", default="", ) # the action value contains the semicolon-separated list of valid # facets valid_facets = valid_facets_action_value.split(";") # Prepare the response response.content_type = ( "application/fido.trusted-apps+json" # as specified by FIDO ) response_dict = { "trustedFacets": [{ "version": { "major": 1, "minor": 0 }, "ids": [] }] } for facet in valid_facets: facet = facet.strip() response_dict["trustedFacets"][0]["ids"].append(facet) return json.dumps(response_dict)
def _is_valid_facet(self, origin): """ check if origin is in the valid facets if the u2f_valid_facets policy is set. Otherwise check if the origin matches the previously saved origin :return: boolean - True if supported, False if unsupported """ is_valid = False # Get the valid facets as specified in the enrollment policy 'u2f_valid_facets' # for the specific realm valid_facets_action_value = "" realms = self.token.getRealmNames() if len(realms) > 0: get_policy_params = { "action": "u2f_valid_facets", "scope": "enrollment", "realm": realms[0], } policies = getPolicy(get_policy_params) valid_facets_action_value = get_action_value( policies, scope="enrollment", action="u2f_valid_facets", default="", ) if valid_facets_action_value != "": # 'u2f_valid_facets' policy is set - check if origin is in valid facets list valid_facets = valid_facets_action_value.split(";") for facet in valid_facets: facet = facet.strip() if origin in valid_facets: is_valid = True else: # 'u2f_valid_facets' policy is empty or not set # check if origin matches the origin stored in the token info or save it if no origin # is stored yet appId = self._get_app_id() if appId == origin: is_valid = True return is_valid
def _is_valid_facet(self, origin): """ check if origin is in the valid facets if the u2f_valid_facets policy is set. Otherwise check if the origin matches the previously saved origin :return: boolean - True if supported, False if unsupported """ is_valid = False # Get the valid facets as specified in the enrollment policy 'u2f_valid_facets' # for the specific realm valid_facets_action_value = '' realms = self.token.getRealmNames() if len(realms) > 0: get_policy_params = { 'action': 'u2f_valid_facets', 'scope': 'enrollment', 'realm': realms[0] } valid_facets_action_value = getPolicyActionValue( getPolicy(get_policy_params), 'u2f_valid_facets', String=True) if valid_facets_action_value != '': # 'u2f_valid_facets' policy is set - check if origin is in valid facets list valid_facets = valid_facets_action_value.split(';') for facet in valid_facets: facet = facet.strip() if origin in valid_facets: is_valid = True else: # 'u2f_valid_facets' policy is empty or not set # check if origin matches the origin stored in the token info or save it if no origin # is stored yet appId = self.getFromTokenInfo('appId', None) if appId is None: self.addToTokenInfo('appId', origin) is_valid = True else: if origin == appId: is_valid = True return is_valid
def is_email_editable(user=""): ''' this function checks the policy scope=selfservice, action=edit_email This is a int policy, while the '0' is a deny ''' ret = True realm = user.realm login = user.login policies = getPolicy({'scope': 'selfservice', 'realm': realm, "action": "edit_email", "user": login},) if policies: edit_email = getPolicyActionValue(policies, "edit_email") if edit_email == 0: ret = False return ret
def do_forward_failcounter(token): """ this function checks the for the policy scope=authentication, action=forwardtoken:no_failcounter_forwarding defining if the target token failcounter should be incremented / reseted :param serial: the token serial number, which allows to derive the realm(s) and owner from :return: boolean """ boolean = True owner = get_token_owner(token) if owner and owner.realm: realms = [owner.realm] else: realms = getTokenRealms(token.getSerial()) if not realms: realms = ["*"] for realm in realms: params = { "scope": "authentication", "realm": realm, "action": "forwardtoken:no_failcounter_forwarding", } if owner and owner.login: params["user"] = owner.login pol = getPolicy(params) if pol: boolean = False break return boolean
def do_forward_failcounter(token): ''' this function checks the for the policy scope=authentication, action=forwardtoken:no_failcounter_forwarding defining if the target token failcounter should be incremented / reseted :param serial: the token serial number, which allows to derive the realm(s) and owner from :return: boolean ''' boolean = True owner = get_token_owner(token) if owner and owner.realm: realms = [owner.realm] else: realms = getTokenRealms(token.getSerial()) if not realms: realms = ['*'] for realm in realms: params = { 'scope': 'authentication', 'realm': realm, 'action': "forwardtoken:no_failcounter_forwarding" } if owner and owner.login: params['user'] = owner.login pol = getPolicy(params) if pol: boolean = False break return boolean
def is_phone_editable(user="", context=None): ''' this function checks the policy scope=selfservice, action=edit_sms This is a int policy, while the '0' is a deny ''' # the default string is the OTP value ret = True realm = user.realm login = user.login policies = getPolicy( { 'scope': 'selfservice', 'realm': realm, "action": "edit_sms", "user": login }, context=context) if policies: edit_sms = getPolicyActionValue(policies, "edit_sms") if edit_sms == 0: ret = False return ret
def getInitDetail(self, params, user=None): """ to complete the token normalisation, the response of the initialisation should be built by the token specific method, the getInitDetails """ response_detail = {} info = self.getInfo() response_detail.update(info) response_detail['serial'] = self.getSerial() # get requested phase requested_phase = getParam(params, "phase", optional=False) if requested_phase == "registration1": # We are in registration phase 1 # We create a 32 bytes otp key (from urandom) # which is used as the registration challenge challenge = base64.urlsafe_b64encode(binascii.unhexlify(self._genOtpKey_(32))) self.addToTokenInfo('challenge', challenge) # save the appId to the TokenInfo # An appId passed as parameter is preferred over an appId defined in a policy appId = '' if 'appid' in params: appId = params.get('appid') else: # No appId passed as parameter - fall back to the policy # Get the appId as specified in the enrollment policy 'u2f_app_id' # for the specific realm # If the token has multiple realms, the appIds are checked for conflicts. # It could be discussed whether the token should use the appId of the default # realm, when the token is not attached to any realms realms = self.token.getRealmNames() for realm in realms: get_policy_params = { 'action': 'u2f_app_id', 'scope': 'enrollment', 'realm': realm } policies = getPolicy(get_policy_params, context=self.context) policy_value = getPolicyActionValue(policies, 'u2f_app_id', String=True ) # Check for appId conflicts if appId and policy_value: if appId != policy_value: log.error("Conflicting appId values in u2f policies.") raise Exception("Conflicting appId values in u2f policies.") appId = policy_value if not appId: log.error("No appId defined.") raise Exception("No appId defined.") self.addToTokenInfo('appId', appId) # create U2F RegisterRequest object and append it to the response as 'message' appId = self._get_app_id() register_request = {'challenge': challenge, 'version': 'U2F_V2', 'appId': appId } response_detail['registerrequest'] = register_request elif requested_phase == "registration2": # We are in registration phase 2 # process the data generated by the u2f compatible token device registerResponse = "" otpkey = None if 'otpkey' in params: otpkey = params.get('otpkey') if otpkey is not None: # otpkey holds the JSON RegisterResponse object as specified by the FIDO Alliance try: registerResponse = json.loads(otpkey) except ValueError as ex: log.exception("Invalid JSON format - value error %r", (ex)) raise Exception('Invalid JSON format') self._handle_client_errors(registerResponse) try: registrationData = registerResponse['registrationData'] clientData = registerResponse['clientData'] except AttributeError as ex: log.exception( "Couldn't find keyword in JSON object - attribute error %r ", (ex)) raise Exception("Couldn't find keyword in JSON object") # registrationData and clientData are urlsafe base64 encoded # correct padding errors (length should be multiples of 4) # fill up the registrationData with '=' to the correct padding registrationData = registrationData + \ ('=' * (4 - (len(registrationData) % 4))) clientData = clientData + ('=' * (4 - (len(clientData) % 4))) registrationData = base64.urlsafe_b64decode( registrationData.encode('ascii')) clientData = base64.urlsafe_b64decode(clientData.encode('ascii')) # parse the raw registrationData according to the specification (userPublicKey, keyHandle, X509cert, signature) = \ self._parseRegistrationData(registrationData) # check the received clientData object if not self._checkClientData( clientData, 'registration', self.getFromTokenInfo('challenge', None)): raise ValueError("Received invalid clientData object. Aborting...") # prepare the applicationParameter and challengeParameter needed for # verification of the registration signature appId = self._get_app_id() applicationParameter = sha256(appId).digest() challengeParameter = sha256(clientData).digest() # verify the registration signature self._validateRegistrationSignature(applicationParameter, challengeParameter, keyHandle, userPublicKey, X509cert, signature ) # save the key handle and the user public key in the Tokeninfo field for # future use self.addToTokenInfo('keyHandle', base64.urlsafe_b64encode(keyHandle)) self.addToTokenInfo('publicKey', base64.urlsafe_b64encode(userPublicKey)) self.addToTokenInfo('counter', '0') self.addToTokenInfo('phase', 'authentication') # remove the registration challenge from the token info self.removeFromTokenInfo('challenge') # Activate the token self.token.LinOtpIsactive = True else: log.error("No otpkey set!") raise ValueError("No otpkey set") else: log.error("Unsupported phase: %s", requested_phase) raise Exception("Unsupported phase: %s", requested_phase) return response_detail
def getInitDetail(self, params, user=None): """ to complete the token normalisation, the response of the initialisation should be built by the token specific method, the getInitDetails """ response_detail = {} info = self.getInfo() response_detail.update(info) response_detail["serial"] = self.getSerial() # get requested phase try: requested_phase = params["phase"] except KeyError: raise ParameterError("Missing parameter: 'phase'") if requested_phase == "registration1": # We are in registration phase 1 # We create a 32 bytes otp key (from urandom) # which is used as the registration challenge challenge = base64.urlsafe_b64encode( binascii.unhexlify(self._genOtpKey_(32)) ) self.addToTokenInfo("challenge", challenge.decode("ascii")) # save the appId to the TokenInfo # An appId passed as parameter is preferred over an appId defined # in a policy appId = "" if "appid" in params: appId = params.get("appid") else: # No appId passed as parameter - fall back to the policy # Get the appId as specified in the enrollment policy 'u2f_app_id' # for the specific realm # If the token has multiple realms, the appIds are checked for conflicts. # It could be discussed whether the token should use the appId of the default # realm, when the token is not attached to any realms realms = self.token.getRealmNames() for realm in realms: get_policy_params = { "action": "u2f_app_id", "scope": "enrollment", "realm": realm, } policies = getPolicy(get_policy_params) policy_value = get_action_value( policies, scope="enrollment", action="u2f_app_id", default="", ) # Check for appId conflicts if appId and policy_value: if appId != policy_value: raise Exception( "Conflicting appId values in u2f policies." ) appId = policy_value if not appId: raise Exception("No appId defined.") self.addToTokenInfo("appId", appId) # create U2F RegisterRequest object and append it to the response # as 'message' appId = self._get_app_id() register_request = { "challenge": challenge.decode("ascii"), "version": "U2F_V2", "appId": appId, } response_detail["registerrequest"] = register_request elif requested_phase == "registration2": # We are in registration phase 2 # process the data generated by the u2f compatible token device registerResponse = "" otpkey = None if "otpkey" in params: otpkey = params.get("otpkey") if otpkey is not None: # otpkey holds the JSON RegisterResponse object as specified by # the FIDO Alliance try: registerResponse = json.loads(otpkey) except ValueError as ex: raise Exception("Invalid JSON format") self._handle_client_errors(registerResponse) try: registrationData = registerResponse["registrationData"] clientData = registerResponse["clientData"] except AttributeError as ex: raise Exception("Couldn't find keyword in JSON object") # registrationData and clientData are urlsafe base64 encoded # correct padding errors (length should be multiples of 4) # fill up the registrationData with '=' to the correct padding registrationData = registrationData + ( "=" * (4 - (len(registrationData) % 4)) ) clientData = clientData + ("=" * (4 - (len(clientData) % 4))) registrationData = base64.urlsafe_b64decode( registrationData.encode("ascii") ) clientData = base64.urlsafe_b64decode( clientData.encode("ascii") ) # parse the raw registrationData according to the specification ( userPublicKey, keyHandle, x509cert, signature, ) = self._parseRegistrationData(registrationData) # check the received clientData object if not self._checkClientData( clientData, "registration", self.getFromTokenInfo("challenge", None), ): raise ValueError( "Received invalid clientData object. Aborting..." ) # prepare the applicationParameter and challengeParameter needed for # verification of the registration signature appId = self._get_app_id() applicationParameter = sha256(appId.encode("utf-8")).digest() challengeParameter = sha256(clientData).digest() # verify the registration signature self._validateRegistrationSignature( applicationParameter, challengeParameter, keyHandle, userPublicKey, x509cert, signature, ) # save the key handle and the user public key in the Tokeninfo field for # future use self.addToTokenInfo( "keyHandle", base64.urlsafe_b64encode(keyHandle).decode("ascii"), ) self.addToTokenInfo( "publicKey", base64.urlsafe_b64encode(userPublicKey).decode("ascii"), ) self.addToTokenInfo("counter", "0") self.addToTokenInfo("phase", "authentication") # remove the registration challenge from the token info self.removeFromTokenInfo("challenge") # Activate the token self.token.LinOtpIsactive = True else: raise ValueError("No otpkey set") else: raise Exception("Unsupported phase: %s", requested_phase) return response_detail