예제 #1
0
    def loadConfig(self, config):
        """
        Load the config from conf.

        :param config: The configuration from the Config Table
        :type config: dict


        '#ldap_uri': 'LDAPURI',
        '#ldap_basedn': 'LDAPBASE',
        '#ldap_binddn': 'BINDDN',
        '#ldap_password': '******',
        '#ldap_timeout': 'TIMEOUT',
        '#ldap_sizelimit': 'SIZELIMIT',
        '#ldap_loginattr': 'LOGINNAMEATTRIBUTE',
        '#ldap_searchfilter': 'LDAPSEARCHFILTER',
        '#ldap_mapping': 'USERINFO',
        '#ldap_uidtype': 'UIDTYPE',
        '#ldap_noreferrals' : 'NOREFERRALS',
        '#ldap_editable' : 'EDITABLE',
        '#ldap_certificate': 'CACERTIFICATE',

        """
        self.uri = config.get("LDAPURI")
        self.basedn = config.get("LDAPBASE")
        self.binddn = config.get("BINDDN")
        # object_classes is a comma separated list like
        # ["top", "person", "organizationalPerson", "user", "inetOrgPerson"]
        self.object_classes = [cl.strip() for cl in config.get("OBJECT_CLASSES", "").split(",")]
        self.dn_template = config.get("DN_TEMPLATE", "")
        self.bindpw = config.get("BINDPW")
        self.timeout = float(config.get("TIMEOUT", 5))
        self.cache_timeout = int(config.get("CACHE_TIMEOUT", 120))
        self.sizelimit = int(config.get("SIZELIMIT", 500))
        self.loginname_attribute = config.get("LOGINNAMEATTRIBUTE")
        self.searchfilter = config.get("LDAPSEARCHFILTER")
        userinfo = config.get("USERINFO", "{}")
        self.userinfo = yaml.safe_load(userinfo)
        self.userinfo["username"] = self.loginname_attribute
        self.map = yaml.safe_load(userinfo)
        self.uidtype = config.get("UIDTYPE", "DN")
        self.noreferrals = is_true(config.get("NOREFERRALS", False))
        self.start_tls = is_true(config.get("START_TLS", False))
        self.get_info = get_info_configuration(is_true(config.get("NOSCHEMAS", False)))
        self._editable = config.get("EDITABLE", False)
        self.scope = config.get("SCOPE") or ldap3.SUBTREE
        self.resolverId = self.uri
        self.authtype = config.get("AUTHTYPE", AUTHTYPE.SIMPLE)
        self.tls_verify = is_true(config.get("TLS_VERIFY", False))
        self.tls_ca_file = config.get("TLS_CA_FILE") or DEFAULT_CA_FILE
        if self.tls_verify and (self.uri.lower().startswith("ldaps") or
                                    self.start_tls):
            self.tls_context = Tls(validate=ssl.CERT_REQUIRED,
                                   version=ssl.PROTOCOL_TLSv1,
                                   ca_certs_file=self.tls_ca_file)
        else:
            self.tls_context = None

        return self
예제 #2
0
def get_resolver_list(filter_resolver_type=None,
                      filter_resolver_name=None,
                      editable=None,
                      censor=False):
    """
    Gets the list of configured resolvers from the database

    :param filter_resolver_type: Only resolvers of the given type are returned
    :type filter_resolver_type: basestring
    :param filter_resolver_name: Get the distinct resolver
    :type filter_resolver_name: basestring
    :param editable: Whether only return editable resolvers
    :type editable: bool
    :param censor: censor sensitive data. Each resolver class decides on its own,
        which data should be censored.
    :type censor: bool
    :rtype: Dictionary of the resolvers and their configuration
    """
    # We need to check if we need to update the config object
    config_object = update_config_object()
    if censor:
        resolvers = copy.deepcopy(config_object.resolver)
    else:
        resolvers = config_object.resolver
    if filter_resolver_type:
        reduced_resolvers = {}
        for reso_name, reso in resolvers.items():
            if reso.get("type") == filter_resolver_type:
                reduced_resolvers[reso_name] = resolvers[reso_name]
        resolvers = reduced_resolvers
    if filter_resolver_name:
        reduced_resolvers = {}
        for reso_name in resolvers:
            if reso_name.lower() == filter_resolver_name.lower():
                reduced_resolvers[reso_name] = resolvers[reso_name]
        resolvers = reduced_resolvers
    if editable is not None:
        reduced_resolvers = {}
        if editable is True:
            for reso_name, reso in resolvers.items():
                check_editable = is_true(reso["data"].get("Editable")) or \
                                 is_true(reso["data"].get("EDITABLE"))
                if check_editable:
                    reduced_resolvers[reso_name] = resolvers[reso_name]
        elif editable is False:
            for reso_name, reso in resolvers.items():
                check_editable = is_true(reso["data"].get("Editable")) or \
                                 is_true(reso["data"].get("EDITABLE"))
                if not check_editable:
                    reduced_resolvers[reso_name] = resolvers[reso_name]
        resolvers = reduced_resolvers
    if censor:
        for reso_name, reso in resolvers.items():
            for censor_key in reso.get("censor_keys", []):
                reso["data"][censor_key] = CENSORED

    return resolvers
예제 #3
0
    def update(self, param, reset_failcount=True):
        """
        process the initialization parameters

        Do we really always need an otpkey?
        the otpKey is handled in the parent class
        :param param: dict of initialization parameters
        :type param: dict

        :return: nothing
        """
        # In case am Immutable MultiDict:
        upd_param = {}
        for k, v in param.items():
            upd_param[k] = v

        # Special handling of 2-step enrollment
        if is_true(getParam(param, "2stepinit", optional)):
            # Use the 2step_serversize setting for the size of the server secret
            # (if it is set)
            if "2step_serversize" in upd_param:
                upd_param["keysize"] = int(getParam(upd_param, "2step_serversize", required))
            # Add twostep settings to the tokeninfo
            for key, default in [
                ("2step_difficulty", TWOSTEP_DEFAULT_DIFFICULTY),
                ("2step_clientsize", TWOSTEP_DEFAULT_CLIENTSIZE)]:
                self.add_tokeninfo(key, getParam(param, key, optional, default))

        val = getParam(upd_param, "hashlib", optional)
        if val is not None:
            hashlibStr = val
        else:
            hashlibStr = self.hashlib

        # check if the key_size is provided
        # if not, we could derive it from the hashlib
        key_size = getParam(upd_param, 'key_size', optional) \
                   or getParam(upd_param, 'keysize', optional)
        if key_size is None:
            upd_param['keysize'] = keylen.get(hashlibStr)

        otpKey = getParam(upd_param, "otpkey", optional)
        genkey = is_true(getParam(upd_param, "genkey", optional))
        if genkey and otpKey:
            # The Base TokenClass does not allow otpkey and genkey at the
            # same time
            del upd_param['otpkey']
        upd_param['hashlib'] = hashlibStr
        # We first need to call the parent class. Since exceptions would be
        # raised here.
        TokenClass.update(self, upd_param, reset_failcount)

        self.add_tokeninfo("hashlib", hashlibStr)

        # check the tokenkind
        if self.token.serial.startswith("UB"):
            self.add_tokeninfo("tokenkind", TOKENKIND.HARDWARE)
예제 #4
0
    def test_06_is_true(self):
        self.assertFalse(is_true(None))
        self.assertFalse(is_true(0))
        self.assertFalse(is_true("0"))
        self.assertFalse(is_true(False))
        self.assertFalse(is_true("false"))

        self.assertTrue(is_true(1))
        self.assertTrue(is_true("1"))
        self.assertTrue(is_true("True"))
        self.assertTrue(is_true("TRUE"))
        self.assertTrue(is_true(True))
예제 #5
0
    def test_01_create_token(self):
        db_token = Token(self.serial1, tokentype="sms")
        db_token.save()
        token = SmsTokenClass(db_token)
        token.update({"phone": self.phone1})
        token.save()
        self.assertTrue(token.token.serial == self.serial1, token)
        self.assertTrue(token.token.tokentype == "sms", token.token)
        self.assertTrue(token.type == "sms", token.type)
        class_prefix = token.get_class_prefix()
        self.assertTrue(class_prefix == "PISM", class_prefix)
        self.assertTrue(token.get_class_type() == "sms", token)

        db_token = Token(self.serial2, tokentype="sms")
        db_token.save()
        token = SmsTokenClass(db_token)
        token.update({"dynamic_phone": True})
        token.save()
        self.assertTrue(token.token.serial == self.serial2, token)
        self.assertTrue(token.token.tokentype == "sms", token.token)
        self.assertTrue(is_true(token.get_tokeninfo("dynamic_phone")))
        self.assertTrue(token.type == "sms", token.type)
        class_prefix = token.get_class_prefix()
        self.assertTrue(class_prefix == "PISM", class_prefix)
        self.assertTrue(token.get_class_type() == "sms", token)
        token.set_user(User(login="******",
                            realm=self.realm1))
예제 #6
0
    def check_otp(self, anOtpVal, counter=None, window=None, options=None):
        """
        check the otpval of a token against a given counter
        and the window

        :param passw: the to be verified passw/pin
        :type passw: string

        :return: counter if found, -1 if not found
        :rtype: int
        """
        options = options or {}
        ret = HotpTokenClass.check_otp(self, anOtpVal, counter, window, options)
        if ret < 0 and is_true(get_from_config("email.concurrent_challenges")):
            if options.get("data") == anOtpVal:
                # We authenticate from the saved challenge
                ret = 1
        if ret >= 0 and self._get_auto_email(options):
            message, mimetype = self._get_email_text_or_subject(options)
            subject, _ = self._get_email_text_or_subject(options,
                                                      action=EMAILACTION.EMAILSUBJECT,
                                                      default="Your OTP")
            self.inc_otp_counter(ret, reset=False)
            success, message = self._compose_email(message=message,
                                                   subject=subject,
                                                   mimetype=mimetype)
            log.debug("AutoEmail: send new SMS: {0!s}".format(success))
            log.debug("AutoEmail: {0!r}".format(message))
        return ret
예제 #7
0
    def do(self, params):
        for opt in self.options.keys():
            if is_true(params.get(opt)):
                log.debug("Got param {0}".format(opt))
                write_stats(opt, getattr(self, '_' + opt))

        return True
예제 #8
0
    def update(self, param, reset_failcount=True):
        """
        update - process initialization parameters

        :param param: dict of initialization parameters
        :type param: dict

        :return: nothing
        """
        if self.hKeyRequired is True:
            genkey = is_true(getParam(param, "genkey", optional))
            if not param.get('keysize'):
                param['keysize'] = 16
            if genkey:
                otpKey = generate_otpkey(param['keysize'])
                del param['genkey']
            else:
                # genkey not set: check otpkey is given
                # this will raise an exception if otpkey is not present
                otpKey = getParam(param, "otpkey", required)

            param['otpkey'] = otpKey
            
        # motp token specific
        mOTPPin = getParam(param, "motppin", required)
        self.token.set_user_pin(mOTPPin)

        TokenClass.update(self, param, reset_failcount)

        return
    def test_01_create_token(self):
        db_token = Token(self.serial1, tokentype="email")
        db_token.save()
        token = EmailTokenClass(db_token)
        token.update({"email": self.email})
        token.save()
        self.assertTrue(token.token.serial == self.serial1, token)
        self.assertTrue(token.token.tokentype == "email", token.token)
        self.assertTrue(token.type == "email", token.type)
        class_prefix = token.get_class_prefix()
        self.assertTrue(class_prefix == "PIEM", class_prefix)
        self.assertTrue(token.get_class_type() == "email", token)

        # create token with dynamic email
        db_token = Token(self.serial2, tokentype="email")
        db_token.save()
        token = EmailTokenClass(db_token)
        token.update({"dynamic_email": True})
        token.save()
        self.assertTrue(is_true(token.get_tokeninfo("dynamic_email")))
        self.assertTrue(token.token.serial == self.serial2, token)
        self.assertTrue(token.token.tokentype == "email", token.token)
        self.assertTrue(token.type == "email", token.type)
        class_prefix = token.get_class_prefix()
        self.assertTrue(class_prefix == "PIEM", class_prefix)
        self.assertTrue(token.get_class_type() == "email", token)
        token.add_user(User(login="******",
                            realm=self.realm1))
예제 #10
0
    def update(self, param, reset_failcount=True):
        """
        update - process initialization parameters

        :param param: dict of initialization parameters
        :type param: dict

        :return: nothing
        """
        if is_true(getParam(param, 'genkey', optional)):
            raise ParameterError("Generating OTP keys is not supported")

        upd_param = param.copy()

        # If the OTP key is given, it is given as a 496-character hex string which
        # encodes a 248-byte blob. As we want to set a 248-byte OTPKey (= Blob),
        # we unhexlify the OTP key
        if 'otpkey' in param:
            if len(param['otpkey']) != 496:
                raise ParameterError('Expected OTP key as 496-character hex string, but length is {!s}'.format(
                    len(param['otpkey'])
                ))
            try:
                upd_param['otpkey'] = binascii.unhexlify(upd_param['otpkey'])
            except (binascii.Error, TypeError):
                raise ParameterError('Expected OTP key as 496-character hex string, but it is malformed')

        TokenClass.update(self, upd_param, reset_failcount)
예제 #11
0
def get_resolver_list(filter_resolver_type=None,
                      filter_resolver_name=None,
                      editable=None):
    """
    Gets the list of configured resolvers from the database

    :param filter_resolver_type: Only resolvers of the given type are returned
    :type filter_resolver_type: basestring
    :param filter_resolver_name: Get the distinct resolver
    :type filter_resolver_name: basestring
    :param editable: Whether only return editable resolvers
    :type editable: bool
    :rtype: Dictionary of the resolvers and their configuration
    """
    # We need to check if we need to update the config object
    config_object = update_config_object()
    resolvers = config_object.resolver
    if filter_resolver_type:
        reduced_resolvers = {}
        for reso_name, reso in resolvers.items():
            if reso.get("type") == filter_resolver_type:
                reduced_resolvers[reso_name] = resolvers[reso_name]
        resolvers = reduced_resolvers
    if filter_resolver_name:
        reduced_resolvers = {}
        for reso_name in resolvers:
            if reso_name.lower() == filter_resolver_name.lower():
                reduced_resolvers[reso_name] = resolvers[reso_name]
        resolvers = reduced_resolvers
    if editable is not None:
        reduced_resolvers = {}
        if editable is True:
            for reso_name, reso in resolvers.items():
                check_editable = is_true(reso["data"].get("Editable")) or \
                                 is_true(reso["data"].get("EDITABLE"))
                if check_editable:
                    reduced_resolvers[reso_name] = resolvers[reso_name]
        elif editable is False:
            for reso_name, reso in resolvers.items():
                check_editable = is_true(reso["data"].get("Editable")) or \
                                 is_true(reso["data"].get("EDITABLE"))
                if not check_editable:
                    reduced_resolvers[reso_name] = resolvers[reso_name]
        resolvers = reduced_resolvers

    return resolvers
예제 #12
0
 def editable(self):
     """
     Return true, if the instance of the resolver is configured editable
     :return:
     """
     # Depending on the database this might look different
     # Usually this is "1"
     return is_true(self._editable)
예제 #13
0
    def create_challenge(self, transactionid=None, options=None):
        """
        create a challenge, which is submitted to the user

        :param transactionid: the id of this challenge
        :param options: the request context parameters / data
                You can pass exception=1 to raise an exception, if
                the SMS could not be sent. Otherwise the message is contained in the response.
        :return: tuple of (bool, message and data)
                 bool, if submit was successful
                 message is submitted to the user
                 data is preserved in the challenge
                 attributes - additional attributes, which are displayed in the
                    output
        """
        success = False
        options = options or {}
        return_message = get_action_values_from_options(SCOPE.AUTH,
                                                        "{0!s}_{1!s}".format(self.get_class_type(),
                                                                             ACTION.CHALLENGETEXT),
                                                        options) or _("Enter the OTP from the SMS:")
        attributes = {'state': transactionid}
        validity = self._get_sms_timeout()

        if self.is_active() is True:
            counter = self.get_otp_count()
            log.debug("counter={0!r}".format(counter))
            self.inc_otp_counter(counter, reset=False)
            # At this point we must not bail out in case of an
            # Gateway error, since checkPIN is successful. A bail
            # out would cancel the checking of the other tokens
            try:
                message_template = self._get_sms_text(options)
                success, sent_message = self._send_sms(
                    message=message_template)

                # Create the challenge in the database
                db_challenge = Challenge(self.token.serial,
                                         transaction_id=transactionid,
                                         challenge=options.get("challenge"),
                                         session=options.get("session"),
                                         validitytime=validity)
                db_challenge.save()
                transactionid = transactionid or db_challenge.transaction_id
            except Exception as e:
                info = ("The PIN was correct, but the "
                        "SMS could not be sent: %r" % e)
                log.warning(info)
                log.debug("{0!s}".format(traceback.format_exc()))
                return_message = info
                if is_true(options.get("exception")):
                    raise Exception(info)

        expiry_date = datetime.datetime.now() + \
                                    datetime.timedelta(seconds=validity)
        attributes['valid_until'] = "{0!s}".format(expiry_date)

        return success, return_message, transactionid, attributes
예제 #14
0
    def check_pin_local(self):
        """
        lookup if pin should be checked locally or on radius host

        :return: bool
        """
        local_check = is_true(self.get_tokeninfo("radius.local_checkpin"))
        log.debug("local checking pin? {0!r}".format(local_check))

        return local_check
예제 #15
0
파일: ldap.py 프로젝트: STRML/privacyidea
    def load_config(self, config):
        """
        This loads the configuration dictionary, which contains the necessary
        information for the machine resolver to find and connect to the
        machine store.

        class=computer or sAMAccountType=805306369 (MachineAccount)
        * hostname: attribute dNSHostName
        * id: DN or objectSid
        * ip: N/A

        :param config: The configuration dictionary to run the machine resolver
        :type config: dict
        :return: None
        """
        self.uri = config.get("LDAPURI")
        if self.uri is None:
            raise MachineResolverError("LDAPURI is missing!")
        self.basedn = config.get("LDAPBASE")
        if self.basedn is None:
            raise MachineResolverError("LDAPBASE is missing!")
        self.binddn = config.get("BINDDN")
        self.bindpw = config.get("BINDPW")
        self.timeout = float(config.get("TIMEOUT", 5))
        self.sizelimit = config.get("SIZELIMIT", 500)
        self.hostname_attribute = config.get("HOSTNAMEATTRIBUTE")
        self.id_attribute = config.get("IDATTRIBUTE", "DN")
        self.ip_attribute = config.get("IPATTRIBUTE")
        self.search_filter = config.get("SEARCHFILTER",
                                        "(objectClass=computer)")

        self.noreferrals = is_true(config.get("NOREFERRALS", False))
        self.authtype = config.get("AUTHTYPE", AUTHTYPE.SIMPLE)
        self.start_tls = is_true(config.get("START_TLS", False))
        self.tls_verify = is_true(config.get("TLS_VERIFY", False))
        self.tls_ca_file = config.get("TLS_CA_FILE") or DEFAULT_CA_FILE
        if self.tls_verify and (self.uri.lower().startswith("ldaps") or
                                    self.start_tls):
            self.tls_context = Tls(validate=ssl.CERT_REQUIRED,
                                   version=ssl.PROTOCOL_TLSv1,
                                   ca_certs_file=self.tls_ca_file)
        else:
            self.tls_context = None
예제 #16
0
 def _email_address(self):
     if is_true(self.get_tokeninfo("dynamic_email")):
         email = self.user.info.get(self.EMAIL_ADDRESS_KEY)
         if type(email) == list and email:
             # If there is a non-empty list, we use the first entry
             email = email[0]
     else:
         email = self.get_tokeninfo(self.EMAIL_ADDRESS_KEY)
     if not email:  # pragma: no cover
         log.warning("Token {0!s} does not have an email address!".format(self.token.serial))
     return email
예제 #17
0
파일: ldap.py 프로젝트: STRML/privacyidea
    def testconnection(params):
        """
        Test if the given filename exists.

        :param params:
        :return:
        """
        success = False
        ldap_uri = params.get("LDAPURI")
        if is_true(params.get("TLS_VERIFY")) \
                and (ldap_uri.lower().startswith("ldaps") or
                                    params.get("START_TLS")):
            tls_ca_file = params.get("TLS_CA_FILE") or DEFAULT_CA_FILE
            tls_context = Tls(validate=ssl.CERT_REQUIRED,
                              version=ssl.PROTOCOL_TLSv1,
                              ca_certs_file=tls_ca_file)
        else:
            tls_context = None
        try:
            server_pool = IdResolver.get_serverpool(ldap_uri,
                                                    float(params.get(
                                                        "TIMEOUT", 5)),
                                                    tls_context=tls_context)
            l = IdResolver.create_connection(authtype=\
                                                 params.get("AUTHTYPE",
                                                            AUTHTYPE.SIMPLE),
                                             server=server_pool,
                                             user=params.get("BINDDN"),
                                             password=params.get("BINDPW"),
                                             auto_referrals=not params.get(
                                                 "NOREFERRALS"),
                                             start_tls=params.get("START_TLS", False))
            if not l.bind():
                raise Exception("Wrong credentials")
            # search for users...
            l.search(search_base=params["LDAPBASE"],
                     search_scope=ldap3.SUBTREE,
                     search_filter="(&" + params["SEARCHFILTER"] + ")",
                     attributes=[ params["HOSTNAMEATTRIBUTE"] ])

            count = len([x for x in l.response if x.get("type") ==
                         "searchResEntry"])
            desc = _("Your LDAP config seems to be OK, %i machine objects "
                     "found.")\
                % count

            l.unbind()
            success = True

        except Exception as e:
            desc = "{0!r}".format(e)

        return success, desc
예제 #18
0
def twostep_enrollment_parameters(request=None, action=None):
    """
    If the ``2stepinit`` parameter is set to true, this policy function
    reads additional configuration from policies and adds it
    to ``request.all_data``, that is:

     * ``{type}_2step_serversize`` is written to ``2step_serversize``
     * ``{type}_2step_clientsize`` is written to ``2step_clientsize`
     * ``{type}_2step_difficulty`` is written to ``2step_difficulty``

    If no policy matches, the value passed by the user is kept.

    This policy function is used to decorate the ``/token/init`` endpoint.
    """
    policy_object = g.policy_object
    user_object = get_user_from_param(request.all_data)
    serial = getParam(request.all_data, "serial", optional)
    token_type = getParam(request.all_data, "type", optional, "hotp")
    if serial:
        tokensobject_list = get_tokens(serial=serial)
        if len(tokensobject_list) == 1:
            token_type = tokensobject_list[0].token.tokentype
    token_type = token_type.lower()
    role = g.logged_in_user.get("role")
    # Differentiate between an admin enrolling a token for the
    # user and a user self-enrolling a token.
    if role == ROLE.ADMIN:
        adminrealm = g.logged_in_user.get("realm")
    else:
        adminrealm = None
    realm = user_object.realm
    # In any case, the policy's user attribute is matched against the
    # currently logged-in user (which may be the admin or the
    # self-enrolling user).
    user = g.logged_in_user.get("username")
    # Tokentypes have separate twostep actions
    if is_true(getParam(request.all_data, "2stepinit", optional)):
        parameters = ("2step_serversize", "2step_clientsize", "2step_difficulty")
        for parameter in parameters:
            action = u"{}_{}".format(token_type, parameter)
            action_values = policy_object.get_action_values(action=action,
                                                            scope=SCOPE.ENROLL,
                                                            unique=True,
                                                            user=user,
                                                            realm=realm,
                                                            client=g.client_ip,
                                                            adminrealm=adminrealm)
            if action_values:
                request.all_data[parameter] = action_values[0]
예제 #19
0
    def do(self, params):
        event_counter = params.get("event_counter")
        stats_key = params.get("stats_key")
        reset_event_counter = params.get("reset_event_counter")

        counter_value = read(event_counter)
        if is_true(reset_event_counter) and counter_value:
            reset(event_counter)

        # now write the current value of the counter
        if counter_value is None:
            log.warning(u"Trying to create statistics of a counter_value '{0}', "
                        u"that does not exist.".format(event_counter))
        else:
            write_stats(stats_key, counter_value)

        return True
예제 #20
0
def set_eventhandling():
    """
    This creates a new event handling definition

    :param name: A describing name of the event.bool
    :param id: (optional) when updating an existing event you need to
        specify the id
    :param event: A comma seperated list of events
    :param handlermodule: A handlermodule
    :param action: The action to perform
    :param ordering: An integer number
    :param position: "pre" or "post"
    :param conditions: Conditions, when the event will trigger
    :param options.: A list of possible options.
    """
    param = request.all_data
    name = getParam(param, "name", optional=False)
    event = getParam(param, "event", optional=False)
    eid = getParam(param, "id", optional=True)
    active = is_true(getParam(param, "active", default=True))
    if eid:
        eid = int(eid)
    handlermodule = getParam(param, "handlermodule", optional=False)
    action = getParam(param, "action", optional=False)
    ordering = getParam(param, "ordering", optional=True, default=0)
    position = getParam(param, "position", optional=True, default="post")
    conditions = getParam(param, "conditions", optional=True, default={})
    if type(conditions) is not dict:
        conditions = json.loads(conditions)
    options = {}
    for k, v in param.items():
        if k.startswith("option."):
            options[k[7:]] = v

    res = set_event(name, event, handlermodule=handlermodule,
                    action=action, conditions=conditions,
                    ordering=ordering, id=eid, options=options, active=active,
                    position=position)
    g.audit_object.log({"success": True,
                        "info": res})
    return send_result(res)
예제 #21
0
def set_periodic_task_api():
    """
    Create or replace an existing periodic task definition.

    :param id: ID of an existing periodic task definition that should be updated
    :param name: Name of the periodic task
    :param active: true if the periodic task should be active
    :param interval: Interval at which the periodic task should run (in cron syntax)
    :param nodes: Comma-separated list of nodes on which the periodic task should run
    :param taskmodule: Task module name of the task
    :param ordering: Ordering of the task, must be a number >= 0.
    :param options: A dictionary (possibly JSON) of periodic task options, mapping unicodes to unicodes
    :return: ID of the periodic task
    """
    param = request.all_data
    ptask_id = getParam(param, "id", optional=True)
    if ptask_id is not None:
        ptask_id = int(ptask_id)
    name = getParam(param, "name", optional=False)
    active = is_true(getParam(param, "active", default=True))
    interval = getParam(param, "interval", optional=False)
    node_string = getParam(param, "nodes", optional=False)
    if node_string.strip():
        node_list = [node.strip() for node in node_string.split(",")]
    else:
        raise ParameterError(u"nodes: expected at least one node")
    taskmodule = getParam(param, "taskmodule", optional=False)
    if taskmodule not in get_available_taskmodules():
        raise ParameterError("Unknown task module: {!r}".format(taskmodule))
    ordering = int(getParam(param, "ordering", optional=False))
    options = getParam(param, "options", optional=True)
    if options is None:
        options = {}
    elif not isinstance(options, dict):
        options = json.loads(options)
        if not isinstance(options, dict):
            raise ParameterError(u"options: expected dictionary, got {!r}".format(options))
    result = set_periodic_task(name, interval, node_list, taskmodule, ordering, options, active, ptask_id)
    g.audit_object.log({"success": True, "info": result})
    return send_result(result)
예제 #22
0
    def update(self, param, reset_failcount=True):
        """
        This method is called during the initialization process.

        :param param: parameters from the token init
        :type param: dict
        :return: None
        """
        TokenClass.update(self, param)
        description = "U2F initialization"
        reg_data = getParam(param, "regdata")
        verify_cert = is_true(getParam(param, "u2f.verify_cert", default=True))
        if reg_data:
            self.init_step = 2
            attestation_cert, user_pub_key, key_handle, \
                signature, description = parse_registration_data(reg_data,
                                                                 verify_cert=verify_cert)
            client_data = getParam(param, "clientdata", required)
            client_data_str = url_decode(client_data)
            app_id = self.get_tokeninfo("appId", "")
            # Verify the registration data
            # In case of any crypto error, check_data raises an exception
            check_registration_data(attestation_cert, app_id, client_data_str,
                                    user_pub_key, key_handle, signature)
            self.set_otpkey(key_handle)
            self.add_tokeninfo("pubKey", user_pub_key)
            # add attestation certificat info
            issuer = x509name_to_string(attestation_cert.get_issuer())
            serial = "{!s}".format(attestation_cert.get_serial_number())
            subject = x509name_to_string(attestation_cert.get_subject())

            self.add_tokeninfo("attestation_issuer", issuer)
            self.add_tokeninfo("attestation_serial", serial)
            self.add_tokeninfo("attestation_subject", subject)

        # If a description is given we use the given description
        description = getParam(param, "description", default=description)
        self.set_description(description)
예제 #23
0
    def check_otp(self, anOtpVal, counter=None, window=None, options=None):
        """
        check the otpval of a token against a given counter
        and the window

        :param passw: the to be verified passw/pin
        :type passw: string

        :return: counter if found, -1 if not found
        :rtype: int
        """
        options = options or {}
        ret = HotpTokenClass.check_otp(self, anOtpVal, counter, window, options)
        if ret < 0 and is_true(get_from_config("sms.concurrent_challenges")):
            if options.get("data") == anOtpVal:
                # We authenticate from the saved challenge
                ret = 1
        if ret >= 0 and self._get_auto_sms(options):
            message = self._get_sms_text(options)
            self.inc_otp_counter(ret, reset=False)
            success, message = self._send_sms(message=message)
            log.debug("AutoSMS: send new SMS: {0!s}".format(success))
            log.debug("AutoSMS: {0!r}".format(message))
        return ret
예제 #24
0
    def check_condition(self, options):
        """
        Check if all conditions are met and if the action should be executed.
        The the conditions are met, we return "True"
        :return: True
        """
        g = options.get("g")
        request = options.get("request")
        response = options.get("response")
        e_handler_def = options.get("handler_def")
        if not e_handler_def:
            # options is the handler definition
            return True
        # conditions can be corresponding to the property conditions
        conditions = e_handler_def.get("conditions")
        content = self._get_response_content(response)
        user = self._get_tokenowner(request)

        serial = request.all_data.get("serial") or \
                 content.get("detail", {}).get("serial")
        tokenrealms = []
        tokentype = None
        token_obj = None
        if serial:
            # We have determined the serial number from the request.
            token_obj_list = get_tokens(serial=serial)
        else:
            # We have to determine the token via the user object. But only if
            #  the user has only one token
            token_obj_list = get_tokens(user=user)
        if len(token_obj_list) == 1:
            token_obj = token_obj_list[0]
            tokenrealms = token_obj.get_realms()
            tokentype = token_obj.get_tokentype()

        if "realm" in conditions:
            if user.realm != conditions.get("realm"):
                return False

        if "logged_in_user" in conditions:
            # Determine the role of the user
            try:
                logged_in_user = g.logged_in_user
                user_role = logged_in_user.get("role")
            except Exception:
                # A non-logged-in-user is a User, not an admin
                user_role = ROLE.USER
            if user_role != conditions.get("logged_in_user"):
                return False

        if CONDITION.RESULT_VALUE in conditions:
            condition_value = conditions.get(CONDITION.RESULT_VALUE)
            result_value = content.get("result", {}).get("value")
            if is_true(condition_value) != is_true(result_value):
                return False

        if CONDITION.RESULT_STATUS in conditions:
            condition_value = conditions.get(CONDITION.RESULT_STATUS)
            result_status = content.get("result", {}).get("status")
            if is_true(condition_value) != is_true(result_status):
                return False

        # checking of max-failcounter state of the token
        if "token_locked" in conditions:
            if token_obj:
                locked = token_obj.get_failcount() >= \
                         token_obj.get_max_failcount()
                if (conditions.get("token_locked") in ["True", True]) != \
                      locked:
                    return False
            else:
                # check all tokens of the user, if any token is maxfail
                token_objects = get_tokens(user=user, maxfail=True)
                if not ','.join([tok.get_serial() for tok in token_objects]):
                    return False

        if "tokenrealm" in conditions and tokenrealms:
            res = False
            for trealm in tokenrealms:
                if trealm in conditions.get("tokenrealm").split(","):
                    res = True
                    break
            if not res:
                return False

        if "serial" in conditions and serial:
            serial_match = conditions.get("serial")
            if not bool(re.match(serial_match, serial)):
                return False

        if CONDITION.USER_TOKEN_NUMBER in conditions and user:
            num_tokens = get_tokens(user=user, count=True)
            if num_tokens != int(conditions.get(
                    CONDITION.USER_TOKEN_NUMBER)):
                return False

        if CONDITION.DETAIL_ERROR_MESSAGE in conditions:
            message = content.get("detail", {}).get("error", {}).get("message")
            search_exp = conditions.get(CONDITION.DETAIL_ERROR_MESSAGE)
            m = re.search(search_exp, message)
            if not bool(m):
                return False

        if CONDITION.DETAIL_MESSAGE in conditions:
            message = content.get("detail", {}).get("message")
            search_exp = conditions.get(CONDITION.DETAIL_MESSAGE)
            m = re.search(search_exp, message)
            if not bool(m):
                return False

        # Token specific conditions
        if token_obj:
            if CONDITION.TOKENTYPE in conditions:
                if tokentype not in conditions.get(CONDITION.TOKENTYPE).split(
                        ","):
                    return False

            if CONDITION.TOKEN_HAS_OWNER in conditions:
                uid = token_obj.get_user_id()
                check = conditions.get(CONDITION.TOKEN_HAS_OWNER)
                if uid and check in ["True", True]:
                    res = True
                elif not uid and check in ["False", False]:
                    res = True
                else:
                    log.debug("Condition token_has_owner for token {0!r} "
                              "not fulfilled.".format(token_obj))
                    return False

            if CONDITION.TOKEN_IS_ORPHANED in conditions:
                uid = token_obj.get_user_id()
                orphaned = uid and not user
                check = conditions.get(CONDITION.TOKEN_IS_ORPHANED)
                if orphaned and check in ["True", True]:
                    res = True
                elif not orphaned and check in ["False", False]:
                    res = True
                else:
                    log.debug("Condition token_is_orphaned for token {0!r} not "
                              "fulfilled.".format(token_obj))
                    return False

            if CONDITION.TOKEN_VALIDITY_PERIOD in conditions:
                valid = token_obj.check_validity_period()
                if (conditions.get(CONDITION.TOKEN_VALIDITY_PERIOD)
                       in ["True", True]) != valid:
                    return False

            if CONDITION.OTP_COUNTER in conditions:
                if token_obj.token.count != \
                      int(conditions.get(CONDITION.OTP_COUNTER)):
                    return False

            if CONDITION.LAST_AUTH in conditions:
                if token_obj.check_last_auth_newer(conditions.get(
                    CONDITION.LAST_AUTH)):
                    return False

            if CONDITION.COUNT_AUTH in conditions:
                count = token_obj.get_count_auth()
                cond = conditions.get(CONDITION.COUNT_AUTH)
                if not compare_condition(cond, count):
                    return False

            if CONDITION.COUNT_AUTH_SUCCESS in conditions:
                count = token_obj.get_count_auth_success()
                cond = conditions.get(CONDITION.COUNT_AUTH_SUCCESS)
                if not compare_condition(cond, count):
                    return False

            if CONDITION.COUNT_AUTH_FAIL in conditions:
                count = token_obj.get_count_auth()
                c_success = token_obj.get_count_auth_success()
                c_fail = count - c_success
                cond = conditions.get(CONDITION.COUNT_AUTH_FAIL)
                if not compare_condition(cond, c_fail):
                    return False

            if CONDITION.TOKENINFO in conditions:
                cond = conditions.get(CONDITION.TOKENINFO)
                # replace {now} in condition
                cond, td = parse_time_offset_from_now(cond)
                s_now = (datetime.datetime.now(tzlocal()) + td).strftime(
                    DATE_FORMAT)
                cond = cond.format(now=s_now)
                if len(cond.split("==")) == 2:
                    key, value = [x.strip() for x in cond.split("==")]
                    if not compare_value_value(token_obj.get_tokeninfo(key),
                                              "==", value):
                        return False
                elif len(cond.split(">")) == 2:
                    key, value = [x.strip() for x in cond.split(">")]
                    if not compare_value_value(token_obj.get_tokeninfo(key),
                                              ">", value):
                        return False
                elif len(cond.split("<")) == 2:
                    key, value = [x.strip() for x in cond.split("<")]
                    if not compare_value_value(token_obj.get_tokeninfo(key),
                                              "<", value):
                        return False
                else:
                    # There is a condition, but we do not know it!
                    log.warning("Misconfiguration in your tokeninfo "
                                "condition: {0!s}".format(cond))
                    return False

        return True
예제 #25
0
    def do(self, action, options=None):
        """
        This method executes the defined action in the given event.

        :param action:
        :param options: Contains the flask parameters g, request, response
            and the handler_def configuration
        :type options: dict
        :return:
        """
        ret = True
        script_name = self.script_directory + "/" + action
        proc_args = [script_name]

        g = options.get("g")
        request = options.get("request")
        response = options.get("response")
        content = self._get_response_content(response)
        handler_def = options.get("handler_def")
        handler_options = handler_def.get("options", {})

        if hasattr(g, "logged_in_user"):
            logged_in_user = g.logged_in_user
        else:
            logged_in_user = {
                "username": "******",
                "realm": "none",
                "role": "none"
            }

        serial = request.all_data.get("serial") or \
                 content.get("detail", {}).get("serial") or \
                 g.audit_object.audit_data.get("serial")

        if handler_options.get("serial"):
            proc_args.append("--serial")
            proc_args.append(serial or "none")

        if handler_options.get("user"):
            proc_args.append("--user")
            proc_args.append(request.User.login or "none")

        if handler_options.get("realm"):
            proc_args.append("--realm")
            proc_args.append(request.User.realm or "none")

        if handler_options.get("logged_in_user"):
            proc_args.append("--logged_in_user")
            proc_args.append("{username}@{realm}".format(**logged_in_user))

        if handler_options.get("logged_in_role"):
            proc_args.append("--logged_in_role")
            proc_args.append(logged_in_user.get("role", "none"))

        rcode = 0
        try:
            log.info("Starting script {script!r}.".format(script=script_name))
            db.session.commit()
            p = subprocess.Popen(proc_args,
                                 cwd=self.script_directory,
                                 universal_newlines=True)
            if handler_options.get("background") == SCRIPT_WAIT:
                rcode = p.wait()

        except Exception as e:
            log.warning("Failed to execute script {0!r}: {1!r}".format(
                script_name, e))
            log.warning(traceback.format_exc())
            if handler_options.get("background") == SCRIPT_WAIT and is_true(
                    handler_options.get("raise_error")):
                raise ServerError("Failed to start script.")

        if rcode:
            log.warning(
                "Script {script!r} failed to execute with error code {error!r}"
                .format(script=script_name, error=rcode))
            if is_true(handler_options.get("raise_error")):
                raise ServerError("Error during execution of the script.")

        return ret
예제 #26
0
    def get_init_detail(self, params=None, user=None):
        """
        to complete the token initialization some additional details
        should be returned, which are displayed at the end of
        the token initialization.
        This is the e.g. the enrollment URL for a Google Authenticator.
        """
        response_detail = TokenClass.get_init_detail(self, params, user)
        params = params or {}
        tokenlabel = params.get("tokenlabel", "<s>")
        tokenissuer = params.get("tokenissuer", "privacyIDEA")
        # If the init_details contain an OTP key the OTP key
        # should be displayed as an enrollment URL
        otpkey = self.init_details.get('otpkey')
        # Add rollout state the response
        response_detail['rollout_state'] = self.token.rollout_state
        # Add two-step initialization parameters to response and QR code
        extra_data = {}
        if is_true(params.get("2stepinit")):
            twostep_parameters = self._get_twostep_parameters()
            extra_data.update(twostep_parameters)
            response_detail.update(twostep_parameters)
        imageurl = params.get("appimageurl")
        if imageurl:
            extra_data.update({"image": imageurl})
        force_app_pin = params.get('force_app_pin')
        if force_app_pin:
            extra_data.update({'pin': True})
        if otpkey:
            tok_type = self.type.lower()
            if user is not None:
                try:
                    key_bin = binascii.unhexlify(otpkey)
                    # also strip the padding =, as it will get problems with the google app.
                    value_b32_str = b32encode_and_unicode(key_bin).strip('=')
                    response_detail["otpkey"]["value_b32"] = value_b32_str
                    goo_url = cr_google(key=otpkey,
                                        user=user.login,
                                        realm=user.realm,
                                        tokentype=tok_type.lower(),
                                        serial=self.get_serial(),
                                        tokenlabel=tokenlabel,
                                        hash_algo=params.get(
                                            "hashlib", "sha1"),
                                        digits=params.get("otplen", 6),
                                        period=params.get("timeStep", 30),
                                        issuer=tokenissuer,
                                        user_obj=user,
                                        extra_data=extra_data)
                    response_detail["googleurl"] = {
                        "description": _("URL for google "
                                         "Authenticator"),
                        "value": goo_url,
                        "img": create_img(goo_url, width=250)
                    }

                    oath_url = cr_oath(otpkey=otpkey,
                                       user=user.login,
                                       realm=user.realm,
                                       type=tok_type,
                                       serial=self.get_serial(),
                                       tokenlabel=tokenlabel,
                                       extra_data=extra_data)
                    response_detail["oathurl"] = {
                        "description": _("URL for"
                                         " OATH "
                                         "token"),
                        "value": oath_url,
                        "img": create_img(oath_url, width=250)
                    }
                except Exception as ex:  # pragma: no cover
                    log.error("{0!s}".format((traceback.format_exc())))
                    log.error(
                        'failed to set oath or google url: {0!r}'.format(ex))

        return response_detail
예제 #27
0
    def get_init_detail(self, params=None, user=None):
        """
        to complete the token initialization some additional details
        should be returned, which are displayed at the end of
        the token initialization.
        This is the e.g. the enrollment URL for a Google Authenticator.
        """
        response_detail = TokenClass.get_init_detail(self, params, user)
        params = params or {}
        tokenlabel = params.get("tokenlabel", "<s>")
        tokenissuer = params.get("tokenissuer", "privacyIDEA")
        # If the init_details contain an OTP key the OTP key
        # should be displayed as an enrollment URL
        otpkey = self.init_details.get('otpkey')
        # Add rollout state the response
        response_detail['rollout_state'] = self.token.rollout_state
        # Add two-step initialization parameters to response and QR code
        extra_data = {}
        if is_true(params.get("2stepinit")):
            twostep_parameters = self._get_twostep_parameters()
            extra_data.update(twostep_parameters)
            response_detail.update(twostep_parameters)
        imageurl = params.get("appimageurl")
        if imageurl:
            extra_data.update({"image": imageurl})
        if otpkey:
            tok_type = self.type.lower()
            if user is not None:                               
                try:
                    key_bin = binascii.unhexlify(otpkey)
                    # also strip the padding =, as it will get problems with the google app.
                    value_b32_str = b32encode_and_unicode(key_bin).strip('=')
                    response_detail["otpkey"]["value_b32"] = value_b32_str
                    goo_url = cr_google(key=otpkey,
                                        user=user.login,
                                        realm=user.realm,
                                        tokentype=tok_type.lower(),
                                        serial=self.get_serial(),
                                        tokenlabel=tokenlabel,
                                        hash_algo=params.get("hashlib", "sha1"),
                                        digits=params.get("otplen", 6),
                                        period=params.get("timeStep", 30),
                                        issuer=tokenissuer,
                                        user_obj=user,
                                        extra_data=extra_data)
                    response_detail["googleurl"] = {"description":
                                                    _("URL for google "
                                                      "Authenticator"),
                                                    "value": goo_url,
                                                    "img": create_img(goo_url,
                                                                      width=250)
                                                    }

                    oath_url = cr_oath(otpkey=otpkey,
                                       user=user.login,
                                       realm=user.realm,
                                       type=tok_type,
                                       serial=self.get_serial(),
                                       tokenlabel=tokenlabel,
                                       extra_data=extra_data)
                    response_detail["oathurl"] = {"description": _("URL for"
                                                                   " OATH "
                                                                   "token"),
                                                  "value": oath_url,
                                                  "img": create_img(oath_url,
                                                                    width=250)
                                                  }
                except Exception as ex:  # pragma: no cover
                    log.error("{0!s}".format((traceback.format_exc())))
                    log.error('failed to set oath or google url: {0!r}'.format(ex))

        return response_detail
예제 #28
0
    def testconnection(cls, param):
        """
        This function lets you test the to be saved LDAP connection.

        :param param: A dictionary with all necessary parameter to test
                        the connection.
        :type param: dict
        :return: Tuple of success and a description
        :rtype: (bool, string)

        Parameters are:
            BINDDN, BINDPW, LDAPURI, TIMEOUT, LDAPBASE, LOGINNAMEATTRIBUTE,
            LDAPSEARCHFILTER, USERINFO, SIZELIMIT, NOREFERRALS, CACERTIFICATE,
            AUTHTYPE, TLS_VERIFY, TLS_VERSION, TLS_CA_FILE, SERVERPOOL_ROUNDS, SERVERPOOL_SKIP
        """
        success = False
        uidtype = param.get("UIDTYPE")
        timeout = float(param.get("TIMEOUT", 5))
        ldap_uri = param.get("LDAPURI")
        size_limit = int(param.get("SIZELIMIT", 500))
        serverpool_rounds = int(
            param.get("SERVERPOOL_ROUNDS") or SERVERPOOL_ROUNDS)
        serverpool_skip = int(param.get("SERVERPOOL_SKIP") or SERVERPOOL_SKIP)
        if is_true(param.get("TLS_VERIFY")) \
                and (ldap_uri.lower().startswith("ldaps") or
                                    param.get("START_TLS")):
            tls_version = int(param.get("TLS_VERSION") or ssl.PROTOCOL_TLSv1)
            tls_ca_file = param.get("TLS_CA_FILE") or DEFAULT_CA_FILE
            tls_context = Tls(validate=ssl.CERT_REQUIRED,
                              version=tls_version,
                              ca_certs_file=tls_ca_file)
        else:
            tls_context = None
        get_info = get_info_configuration(is_true(param.get("NOSCHEMAS")))
        try:
            server_pool = cls.get_serverpool(ldap_uri,
                                             timeout,
                                             tls_context=tls_context,
                                             get_info=get_info,
                                             rounds=serverpool_rounds,
                                             exhaust=serverpool_skip)
            l = cls.create_connection(
                authtype=param.get("AUTHTYPE", AUTHTYPE.SIMPLE),
                server=server_pool,
                user=param.get("BINDDN"),
                password=param.get("BINDPW"),
                receive_timeout=timeout,
                auto_referrals=not param.get("NOREFERRALS"),
                start_tls=param.get("START_TLS", False))
            #log.error("LDAP Server Pool States: %s" % server_pool.pool_states)
            if not l.bind():
                raise Exception("Wrong credentials")
            # create searchattributes
            attributes = list(yaml.safe_load(param["USERINFO"]).values())
            if uidtype.lower() != "dn":
                attributes.append(str(uidtype))
            # search for users...
            g = l.extend.standard.paged_search(
                search_base=param["LDAPBASE"],
                search_filter=u"(&" + param["LDAPSEARCHFILTER"] + ")",
                search_scope=param.get("SCOPE") or ldap3.SUBTREE,
                attributes=attributes,
                paged_size=100,
                size_limit=size_limit,
                generator=True)
            # returns a generator of dictionaries
            count = 0
            uidtype_count = 0
            for entry in ignore_sizelimit_exception(l, g):
                try:
                    userid = cls._get_uid(entry, uidtype)
                    count += 1
                    if userid:
                        uidtype_count += 1
                except Exception as exx:  # pragma: no cover
                    log.warning("Error during fetching LDAP objects:"
                                " {0!r}".format(exx))
                    log.debug("{0!s}".format(traceback.format_exc()))

            if uidtype_count < count:  # pragma: no cover
                desc = _(
                    "Your LDAP config found {0!s} user objects, but only {1!s} "
                    "with the specified uidtype").format(count, uidtype_count)
            else:
                desc = _("Your LDAP config seems to be OK, {0!s} user objects "
                         "found.").format(count)

            l.unbind()
            success = True

        except Exception as e:
            desc = "{0!r}".format(e)
            log.debug("{0!s}".format(traceback.format_exc()))

        return success, desc
예제 #29
0
    def _send_sms(self, message="<otp>"):
        """
        send sms

        :param message: the sms submit message - could contain placeholders
            like <otp> or <serial>
        :type message: string

        :return: submitted message
        :rtype: string
        """
        if is_true(self.get_tokeninfo("dynamic_phone")):
            phone = self.user.get_user_phone("mobile")
            if type(phone) == list and phone:
                # if there is a non-empty list, we use the first entry
                phone = phone[0]
        else:
            phone = self.get_tokeninfo("phone")
        if not phone:  # pragma: no cover
            log.warning("Token {0!s} does not have a phone number!".format(self.token.serial))
        otp = self.get_otp()[2]
        serial = self.get_serial()

        message = message.replace("<otp>", otp)
        message = message.replace("<serial>", serial)
        log.debug("sending SMS to phone number {0!s} ".format(phone))

        # First we try to get the new SMS gateway config style
        # The token specific identifier has priority over the system wide identifier
        sms_gateway_identifier = self.get_tokeninfo("sms.identifier") or get_from_config("sms.identifier")

        if sms_gateway_identifier:
            # New style
            sms = create_sms_instance(sms_gateway_identifier)

        else:
            # Old style
            (SMSProvider, SMSProviderClass) = self._get_sms_provider()
            log.debug("smsprovider: {0!s}, class: {1!s}".format(SMSProvider,
                                                      SMSProviderClass))

            try:
                sms = get_sms_provider_class(SMSProvider, SMSProviderClass)()
            except Exception as exc:
                log.error("Failed to load SMSProvider: {0!r}".format(exc))
                log.debug("{0!s}".format(traceback.format_exc()))
                raise exc

            try:
                # now we need the config from the env
                log.debug("loading SMS configuration for class {0!s}".format(sms))
                config = self._get_sms_provider_config()
                log.debug("config: {0!r}".format(config))
                sms.load_config(config)
            except Exception as exc:
                log.error("Failed to load sms.providerConfig: {0!r}".format(exc))
                log.debug("{0!s}".format(traceback.format_exc()))
                raise Exception("Failed to load sms.providerConfig: {0!r}".format(exc))

        log.debug("submitMessage: {0!r}, to phone {1!r}".format(message, phone))
        ret = sms.submit_message(phone, message)
        return ret, message
예제 #30
0
    def do(self, action, options=None):
        """
        This method executes the defined action in the given event.

        :param action:
        :param options: Contains the flask parameters g, request, response
            and the handler_def configuration
        :type options: dict
        :return:
        """
        ret = True
        script_name = self.script_directory + "/" + action
        proc_args = [script_name]

        g = options.get("g")
        request = options.get("request")
        response = options.get("response")
        content = self._get_response_content(response)
        handler_def = options.get("handler_def")
        handler_options = handler_def.get("options", {})

        if hasattr(g, "logged_in_user"):
            logged_in_user = g.logged_in_user
        else:
            logged_in_user = {"username": "******",
                              "realm": "none",
                              "role": "none"}

        serial = request.all_data.get("serial") or \
                 content.get("detail", {}).get("serial") or \
                 g.audit_object.audit_data.get("serial")

        if handler_options.get("serial"):
            proc_args.append("--serial")
            proc_args.append(serial or "none")

        if handler_options.get("user"):
            proc_args.append("--user")
            proc_args.append(request.User.login or "none")

        if handler_options.get("realm"):
            proc_args.append("--realm")
            proc_args.append(request.User.realm or "none")

        if handler_options.get("logged_in_user"):
            proc_args.append("--logged_in_user")
            proc_args.append("{username}@{realm}".format(
                **logged_in_user))

        if handler_options.get("logged_in_role"):
            proc_args.append("--logged_in_role")
            proc_args.append(logged_in_user.get("role", "none"))

        rcode = 0
        try:
            log.info("Starting script {script!r}.".format(script=script_name))
            p = subprocess.Popen(proc_args, cwd=self.script_directory)
            if handler_options.get("background") == SCRIPT_WAIT:
                rcode = p.wait()

        except Exception as e:
            log.warning("Failed to execute script {0!r}: {1!r}".format(
                script_name, e))
            log.warning(traceback.format_exc())
            if handler_options.get("background") == SCRIPT_WAIT and is_true(handler_options.get("raise_error")):
                raise ServerError("Failed to start script.")

        if rcode:
            log.warning("Script {script!r} failed to execute with error code {error!r}".format(script=script_name,
                                                                                               error=rcode))
            if is_true(handler_options.get("raise_error")):
                raise ServerError("Error during execution of the script.")

        return ret
예제 #31
0
    def check_otp(self, otpval, counter=None, window=None, options=None):
        """
        run the http request against the remote host

        :param otpval: the OTP value
        :param counter: The counter for counter based otp values
        :type counter: int
        :param window: a counter window
        :type counter: int
        :param options: additional token specific options
        :type options: dict
        :return: counter of the matching OTP value.
        :rtype: int
        """
        otp_count = -1
        otpval = otpval.encode("utf-8")

        remoteServer = self.get_tokeninfo("remote.server") or ""
        remoteServer = remoteServer.encode("utf-8")

        # in preparation of the ability to relocate privacyidea urls,
        # we introduce the remote url path
        remotePath = self.get_tokeninfo("remote.path") or ""
        remotePath = remotePath.strip().encode('utf-8')

        remoteSerial = self.get_tokeninfo("remote.serial") or ""
        remoteSerial = remoteSerial.encode('utf-8')

        remoteUser = self.get_tokeninfo("remote.user") or ""
        remoteUser = remoteUser.encode('utf-8')

        remoteRealm = self.get_tokeninfo("remote.realm") or ""
        remoteRealm = remoteRealm.encode('utf-8')

        remoteResolver = self.get_tokeninfo("remote.resolver") or ""
        remoteResolver = remoteResolver.encode('utf-8')

        ssl_verify = get_from_config(
            "remote.verify_ssl_certificate", False, return_bool=True) or False

        if type(ssl_verify) in [str, unicode]:
            ssl_verify = is_true(ssl_verify.lower())

        # here we also need to check for remote.user and so on....
        log.debug("checking OTP len:%r remotely on server: %r,"
                  " serial: %r, user: %r" %
                  (len(otpval), remoteServer, remoteSerial, remoteUser))
        params = {}

        remotePath = remotePath or "/validate/check"
        if remoteSerial:
            params['serial'] = remoteSerial
        elif remoteUser:
            params['user'] = remoteUser
            params['realm'] = remoteRealm
            params['resolver'] = remoteResolver

        else:
            log.warning("The remote token does neither contain a "
                        "remote.serial nor a remote.user.")
            return otp_count

        params['pass'] = otpval
        request_url = "{0!s}{1!s}".format(remoteServer, remotePath)

        try:
            r = requests.post(request_url, data=params, verify=ssl_verify)

            if r.status_code == requests.codes.ok:
                response = r.json()
                result = response.get("result")
                if result.get("value"):
                    otp_count = 1

        except Exception as exx:  # pragma: no cover
            log.error("Error getting response from "
                      "remote Server (%r): %r" % (request_url, exx))
            log.debug("{0!s}".format(traceback.format_exc()))

        return otp_count
예제 #32
0
    def check_condition(self, options):
        """
        Check if all conditions are met and if the action should be executed.
        The the conditions are met, we return "True"
        :return: True
        """
        g = options.get("g")
        request = options.get("request")
        response = options.get("response")
        e_handler_def = options.get("handler_def")
        if not e_handler_def:
            # options is the handler definition
            return True
        # conditions can be corresponding to the property conditions
        conditions = e_handler_def.get("conditions")
        content = self._get_response_content(response)
        user = self._get_tokenowner(request)

        serial = request.all_data.get("serial") or \
                 content.get("detail", {}).get("serial")
        tokenrealms = []
        tokentype = None
        token_obj = None
        if serial:
            # We have determined the serial number from the request.
            token_obj_list = get_tokens(serial=serial)
        else:
            # We have to determine the token via the user object. But only if
            #  the user has only one token
            token_obj_list = get_tokens(user=user)
        if len(token_obj_list) == 1:
            token_obj = token_obj_list[0]
            tokenrealms = token_obj.get_realms()
            tokentype = token_obj.get_tokentype()

        if "realm" in conditions:
            if user.realm != conditions.get("realm"):
                return False

        if "logged_in_user" in conditions:
            # Determine the role of the user
            try:
                logged_in_user = g.logged_in_user
                user_role = logged_in_user.get("role")
            except Exception:
                # A non-logged-in-user is a User, not an admin
                user_role = ROLE.USER
            if user_role != conditions.get("logged_in_user"):
                return False

        if CONDITION.RESULT_VALUE in conditions:
            condition_value = conditions.get(CONDITION.RESULT_VALUE)
            result_value = content.get("result", {}).get("value")
            if is_true(condition_value) != is_true(result_value):
                return False

        if CONDITION.RESULT_STATUS in conditions:
            condition_value = conditions.get(CONDITION.RESULT_STATUS)
            result_status = content.get("result", {}).get("status")
            if is_true(condition_value) != is_true(result_status):
                return False

        # checking of max-failcounter state of the token
        if "token_locked" in conditions:
            if token_obj:
                locked = token_obj.get_failcount() >= \
                         token_obj.get_max_failcount()
                if (conditions.get("token_locked") in ["True", True]) != \
                      locked:
                    return False
            else:
                # check all tokens of the user, if any token is maxfail
                token_objects = get_tokens(user=user, maxfail=True)
                if not ','.join([tok.get_serial() for tok in token_objects]):
                    return False

        if "tokenrealm" in conditions and tokenrealms:
            res = False
            for trealm in tokenrealms:
                if trealm in conditions.get("tokenrealm").split(","):
                    res = True
                    break
            if not res:
                return False

        if "serial" in conditions and serial:
            serial_match = conditions.get("serial")
            if not bool(re.match(serial_match, serial)):
                return False

        if CONDITION.USER_TOKEN_NUMBER in conditions and user:
            num_tokens = get_tokens(user=user, count=True)
            if num_tokens != int(conditions.get(CONDITION.USER_TOKEN_NUMBER)):
                return False

        if CONDITION.DETAIL_ERROR_MESSAGE in conditions:
            message = content.get("detail", {}).get("error", {}).get("message")
            search_exp = conditions.get(CONDITION.DETAIL_ERROR_MESSAGE)
            m = re.search(search_exp, message)
            if not bool(m):
                return False

        if CONDITION.DETAIL_MESSAGE in conditions:
            message = content.get("detail", {}).get("message")
            search_exp = conditions.get(CONDITION.DETAIL_MESSAGE)
            m = re.search(search_exp, message)
            if not bool(m):
                return False

        # Token specific conditions
        if token_obj:
            if CONDITION.TOKENTYPE in conditions:
                if tokentype not in conditions.get(
                        CONDITION.TOKENTYPE).split(","):
                    return False

            if CONDITION.TOKEN_HAS_OWNER in conditions:
                uid = token_obj.get_user_id()
                check = conditions.get(CONDITION.TOKEN_HAS_OWNER)
                if uid and check in ["True", True]:
                    res = True
                elif not uid and check in ["False", False]:
                    res = True
                else:
                    log.debug("Condition token_has_owner for token {0!r} "
                              "not fulfilled.".format(token_obj))
                    return False

            if CONDITION.TOKEN_IS_ORPHANED in conditions:
                uid = token_obj.get_user_id()
                orphaned = uid and not user
                check = conditions.get(CONDITION.TOKEN_IS_ORPHANED)
                if orphaned and check in ["True", True]:
                    res = True
                elif not orphaned and check in ["False", False]:
                    res = True
                else:
                    log.debug(
                        "Condition token_is_orphaned for token {0!r} not "
                        "fulfilled.".format(token_obj))
                    return False

            if CONDITION.TOKEN_VALIDITY_PERIOD in conditions:
                valid = token_obj.check_validity_period()
                if (conditions.get(CONDITION.TOKEN_VALIDITY_PERIOD)
                        in ["True", True]) != valid:
                    return False

            if CONDITION.OTP_COUNTER in conditions:
                cond = conditions.get(CONDITION.OTP_COUNTER)
                if not compare_condition(cond, token_obj.token.count):
                    return False

            if CONDITION.LAST_AUTH in conditions:
                if token_obj.check_last_auth_newer(
                        conditions.get(CONDITION.LAST_AUTH)):
                    return False

            if CONDITION.COUNT_AUTH in conditions:
                count = token_obj.get_count_auth()
                cond = conditions.get(CONDITION.COUNT_AUTH)
                if not compare_condition(cond, count):
                    return False

            if CONDITION.COUNT_AUTH_SUCCESS in conditions:
                count = token_obj.get_count_auth_success()
                cond = conditions.get(CONDITION.COUNT_AUTH_SUCCESS)
                if not compare_condition(cond, count):
                    return False

            if CONDITION.COUNT_AUTH_FAIL in conditions:
                count = token_obj.get_count_auth()
                c_success = token_obj.get_count_auth_success()
                c_fail = count - c_success
                cond = conditions.get(CONDITION.COUNT_AUTH_FAIL)
                if not compare_condition(cond, c_fail):
                    return False

            if CONDITION.TOKENINFO in conditions:
                cond = conditions.get(CONDITION.TOKENINFO)
                # replace {now} in condition
                cond, td = parse_time_offset_from_now(cond)
                s_now = (datetime.datetime.now(tzlocal()) +
                         td).strftime(DATE_FORMAT)
                cond = cond.format(now=s_now)
                if len(cond.split("==")) == 2:
                    key, value = [x.strip() for x in cond.split("==")]
                    if not compare_value_value(token_obj.get_tokeninfo(key),
                                               "==", value):
                        return False
                elif len(cond.split(">")) == 2:
                    key, value = [x.strip() for x in cond.split(">")]
                    if not compare_value_value(token_obj.get_tokeninfo(key),
                                               ">", value):
                        return False
                elif len(cond.split("<")) == 2:
                    key, value = [x.strip() for x in cond.split("<")]
                    if not compare_value_value(token_obj.get_tokeninfo(key),
                                               "<", value):
                        return False
                else:
                    # There is a condition, but we do not know it!
                    log.warning("Misconfiguration in your tokeninfo "
                                "condition: {0!s}".format(cond))
                    return False

        return True
예제 #33
0
    def update(self, param, reset_failcount=True):
        """
        process the initialization parameters

        Do we really always need an otpkey?
        the otpKey is handled in the parent class
        :param param: dict of initialization parameters
        :type param: dict

        :return: nothing
        """
        # In case am Immutable MultiDict:
        upd_param = {}
        for k, v in param.items():
            upd_param[k] = v

        # Special handling of 2-step enrollment
        if is_true(getParam(param, "2stepinit", optional)):
            # Use the 2step_serversize setting for the size of the server secret
            # (if it is set)
            if "2step_serversize" in upd_param:
                upd_param["keysize"] = int(
                    getParam(upd_param, "2step_serversize", required))
            # Add twostep settings to the tokeninfo
            for key, default in [
                ("2step_difficulty", TWOSTEP_DEFAULT_DIFFICULTY),
                ("2step_clientsize", TWOSTEP_DEFAULT_CLIENTSIZE)
            ]:
                self.add_tokeninfo(key, getParam(param, key, optional,
                                                 default))

        val = getParam(upd_param, "hashlib", optional)
        if val is not None:
            hashlibStr = val
        else:
            hashlibStr = 'sha1'

        # check if the key_size is provided
        # if not, we could derive it from the hashlib
        key_size = getParam(upd_param, 'key_size', optional) \
                   or getParam(upd_param, 'keysize', optional)
        if key_size is None:
            upd_param['keysize'] = keylen.get(hashlibStr)

        otpKey = getParam(upd_param, "otpkey", optional)
        genkey = is_true(getParam(upd_param, "genkey", optional))
        if genkey and otpKey:
            # The Base TokenClass does not allow otpkey and genkey at the
            # same time
            del upd_param['otpkey']
        upd_param['hashlib'] = hashlibStr
        # We first need to call the parent class. Since exceptions would be
        # raised here.
        TokenClass.update(self, upd_param, reset_failcount)

        # add_tokeninfo and set_otplen save the token object to the database.
        self.add_tokeninfo("hashlib", hashlibStr)
        val = getParam(upd_param, "otplen", optional)
        if val is not None:
            self.set_otplen(int(val))
        else:
            self.set_otplen(get_from_config("DefaultOtpLen", 6))
예제 #34
0
    def do(self, action, options=None):
        """
        This method executes the defined action in the given event.

        :param action:
        :param options: Contains the flask parameters g, request, response
            and the handler_def configuration
        :type options: dict
        :return:
        """
        ret = True
        g = options.get("g")
        request = options.get("request")
        response = options.get("response")
        content = self._get_response_content(response)
        handler_def = options.get("handler_def")
        handler_options = handler_def.get("options", {})

        serial = request.all_data.get("serial") or \
                 content.get("detail", {}).get("serial") or \
                 g.audit_object.audit_data.get("serial")

        if action.lower() in [ACTION_TYPE.SET_TOKENREALM,
                              ACTION_TYPE.SET_DESCRIPTION,
                              ACTION_TYPE.DELETE, ACTION_TYPE.DISABLE,
                              ACTION_TYPE.ENABLE, ACTION_TYPE.UNASSIGN,
                              ACTION_TYPE.SET_VALIDITY,
                              ACTION_TYPE.SET_COUNTWINDOW,
                              ACTION_TYPE.SET_TOKENINFO,
                              ACTION_TYPE.SET_FAILCOUNTER,
                              ACTION_TYPE.DELETE_TOKENINFO]:
            if serial:
                log.info("{0!s} for token {1!s}".format(action, serial))
                if action.lower() == ACTION_TYPE.SET_TOKENREALM:
                    realm = handler_options.get("realm")
                    only_realm = is_true(handler_options.get("only_realm"))
                    # Set the realm..
                    log.info("Setting realm of token {0!s} to {1!s}".format(
                        serial, realm))
                    # Add the token realm
                    set_realms(serial, [realm], add=not only_realm)
                elif action.lower() == ACTION_TYPE.DELETE:
                    remove_token(serial=serial)
                elif action.lower() == ACTION_TYPE.DISABLE:
                    enable_token(serial, enable=False)
                elif action.lower() == ACTION_TYPE.ENABLE:
                    enable_token(serial, enable=True)
                elif action.lower() == ACTION_TYPE.UNASSIGN:
                    unassign_token(serial)
                elif action.lower() == ACTION_TYPE.SET_DESCRIPTION:
                    description = handler_options.get("description") or ""
                    description, td = parse_time_offset_from_now(description)
                    s_now = (datetime.datetime.now(tzlocal()) + td).strftime(
                        AUTH_DATE_FORMAT)
                    set_description(serial,
                                    description.format(
                                        current_time=s_now,
                                        now=s_now,
                                        client_ip=g.client_ip,
                                        ua_browser=request.user_agent.browser,
                                        ua_string=request.user_agent.string))
                elif action.lower() == ACTION_TYPE.SET_COUNTWINDOW:
                    set_count_window(serial,
                                     int(handler_options.get("count window",
                                                             50)))
                elif action.lower() == ACTION_TYPE.SET_TOKENINFO:
                    tokeninfo = handler_options.get("value") or ""
                    tokeninfo, td = parse_time_offset_from_now(tokeninfo)
                    s_now = (datetime.datetime.now(tzlocal()) + td).strftime(
                        AUTH_DATE_FORMAT)
                    try:
                        username = request.User.loginname
                        realm = request.User.realm
                    except Exception:
                        username = "******"
                        realm = "N/A"
                    add_tokeninfo(serial, handler_options.get("key"),
                                  tokeninfo.format(
                                      current_time=s_now,
                                      now=s_now,
                                      client_ip=g.client_ip,
                                      username=username,
                                      realm=realm,
                                      ua_browser=request.user_agent.browser,
                                      ua_string=request.user_agent.string))
                elif action.lower() == ACTION_TYPE.DELETE_TOKENINFO:
                    delete_tokeninfo(serial, handler_options.get("key"))
                elif action.lower() == ACTION_TYPE.SET_VALIDITY:
                    start_date = handler_options.get(VALIDITY.START)
                    end_date = handler_options.get(VALIDITY.END)
                    if start_date:
                         d = parse_date(start_date)
                         set_validity_period_start(serial, None,
                                                   d.strftime(DATE_FORMAT))
                    if end_date:
                        d = parse_date(end_date)
                        set_validity_period_end(serial, None,
                                                d.strftime(DATE_FORMAT))
                elif action.lower() == ACTION_TYPE.SET_FAILCOUNTER:
                    try:
                        set_failcounter(serial,
                                        int(handler_options.get("fail counter")))
                    except Exception as exx:
                        log.warning("Misconfiguration: Failed to set fail "
                                    "counter!")
            else:
                log.info("Action {0!s} requires serial number. But no serial "
                         "number could be found in request.")

        if action.lower() == ACTION_TYPE.INIT:
            log.info("Initializing new token")
            init_param = {"type": handler_options.get("tokentype"),
                          "genkey": 1,
                          "realm": handler_options.get("realm", "")}
            user = None
            if is_true(handler_options.get("user")):
                user = self._get_tokenowner(request)
                tokentype = handler_options.get("tokentype")
                # Some tokentypes need additional parameters
                if handler_options.get("additional_params"):
                    add_params = yaml.safe_load(handler_options.get("additional_params"))
                    if type(add_params) == dict:
                        init_param.update(add_params)

                if tokentype == "sms":
                    if handler_options.get("dynamic_phone"):
                        init_param["dynamic_phone"] = 1
                    else:
                        init_param['phone'] = user.get_user_phone(
                            phone_type='mobile', index=0)
                        if not init_param['phone']:
                            log.warning("Enrolling SMS token. But the user "
                                        "{0!r} has no mobile number!".format(user))
                elif tokentype == "email":
                    if handler_options.get("dynamic_email"):
                        init_param["dynamic_email"] = 1
                    else:
                        init_param['email'] = user.info.get("email", "")
                        if not init_param['email']:
                            log.warning("Enrolling EMail token. But the user {0!s}"
                                        "has no email address!".format(user))
                elif tokentype == "motp":
                    init_param['motppin'] = handler_options.get("motppin")

            t = init_token(param=init_param, user=user)
            log.info("New token {0!s} enrolled.".format(t.token.serial))

        return ret
예제 #35
0
    def do(self, action, options=None):
        """
        This method executes the defined action in the given event.

        :param action:
        :param options: Contains the flask parameters g, request, response
            and the handler_def configuration
        :type options: dict
        :return:
        """
        ret = True
        g = options.get("g")
        request = options.get("request")
        response = options.get("response")
        content = json.loads(response.data)
        handler_def = options.get("handler_def")
        handler_options = handler_def.get("options", {})

        serial = request.all_data.get("serial") or \
                 content.get("detail", {}).get("serial") or \
                 g.audit_object.audit_data.get("serial")

        if action.lower() in [ACTION_TYPE.SET_TOKENREALM,
                              ACTION_TYPE.SET_DESCRIPTION,
                              ACTION_TYPE.DELETE, ACTION_TYPE.DISABLE,
                              ACTION_TYPE.ENABLE, ACTION_TYPE.UNASSIGN,
                              ACTION_TYPE.SET_VALIDITY,
                              ACTION_TYPE.SET_COUNTWINDOW,
                              ACTION_TYPE.SET_TOKENINFO]:
            if serial:
                log.info("{0!s} for token {1!s}".format(action, serial))
                if action.lower() == ACTION_TYPE.SET_TOKENREALM:
                    realm = handler_options.get("realm")
                    only_realm = handler_options.get("only_realm")
                    # Set the realm..
                    log.info("Setting realm of token {0!s} to {1!s}".format(
                        serial, realm))
                    # Add the token realm
                    set_realms(serial, [realm], add=True)
                elif action.lower() == ACTION_TYPE.DELETE:
                    remove_token(serial=serial)
                elif action.lower() == ACTION_TYPE.DISABLE:
                    enable_token(serial, enable=False)
                elif action.lower() == ACTION_TYPE.ENABLE:
                    enable_token(serial, enable=True)
                elif action.lower() == ACTION_TYPE.UNASSIGN:
                    unassign_token(serial)
                elif action.lower() == ACTION_TYPE.SET_DESCRIPTION:
                    s_now = datetime.datetime.now(tzlocal()).strftime(AUTH_DATE_FORMAT)
                    set_description(serial,
                                    (handler_options.get("description") or
                                     "").format(current_time=s_now,
                                                client_ip=g.client_ip,
                                                ua_browser=request.user_agent.browser,
                                                ua_string=request.user_agent.string))
                elif action.lower() == ACTION_TYPE.SET_COUNTWINDOW:
                    set_count_window(serial,
                                     int(handler_options.get("count window",
                                                             50)))
                elif action.lower() == ACTION_TYPE.SET_TOKENINFO:
                    s_now = datetime.datetime.now(tzlocal()).strftime(AUTH_DATE_FORMAT)
                    add_tokeninfo(serial, handler_options.get("key"),
                                  (handler_options.get("value") or "").format(
                                      current_time=s_now,
                                      client_ip=g.client_ip,
                                      ua_browser=request.user_agent.browser,
                                      ua_string=request.user_agent.string))
                elif action.lower() == ACTION_TYPE.SET_VALIDITY:
                    start_date = handler_options.get(VALIDITY.START)
                    end_date = handler_options.get(VALIDITY.END)
                    if start_date:
                         d = parse_date(start_date)
                         set_validity_period_start(serial, None,
                                                   d.strftime(DATE_FORMAT))
                    if end_date:
                        d = parse_date(end_date)
                        set_validity_period_end(serial, None,
                                                d.strftime(DATE_FORMAT))

            else:
                log.info("Action {0!s} requires serial number. But no serial "
                         "number could be found in request.")

        if action.lower() == ACTION_TYPE.INIT:
            log.info("Initializing new token")
            init_param = {"type": handler_options.get("tokentype"),
                          "genkey": 1,
                          "realm": handler_options.get("realm", "")}
            user = None
            if is_true(handler_options.get("user")):
                user = self._get_tokenowner(request)
                tokentype = handler_options.get("tokentype")
                # Some tokentypes need additional parameters or otherwise
                # will fail to enroll.
                # TODO: Other tokentypes will require additional parameters
                if tokentype == "sms":
                    init_param['phone'] = user.get_user_phone(
                        phone_type='mobile')
                    if not init_param['phone']:
                        log.warning("Enrolling SMS token. But the user "
                                    "{0!s} has no mobile number!".format(user))
                elif tokentype == "email":
                    init_param['email'] = user.info.get("email", "")
                    if not init_param['email']:
                        log.warning("Enrolling EMail token. But the user {0!s}"
                                    "has no email address!".format(user))
                elif tokentype == "motp":
                    init_param['motppin'] = handler_options.get("motppin")

            t = init_token(param=init_param, user=user)
            log.info("New token {0!s} enrolled.".format(t.token.serial))

        return ret
예제 #36
0
    def loadConfig(self, config):
        """
        Load the config from conf.

        :param config: The configuration from the Config Table
        :type config: dict


        '#ldap_uri': 'LDAPURI',
        '#ldap_basedn': 'LDAPBASE',
        '#ldap_binddn': 'BINDDN',
        '#ldap_password': '******',
        '#ldap_timeout': 'TIMEOUT',
        '#ldap_sizelimit': 'SIZELIMIT',
        '#ldap_loginattr': 'LOGINNAMEATTRIBUTE',
        '#ldap_searchfilter': 'LDAPSEARCHFILTER',
        '#ldap_mapping': 'USERINFO',
        '#ldap_uidtype': 'UIDTYPE',
        '#ldap_noreferrals' : 'NOREFERRALS',
        '#ldap_editable' : 'EDITABLE',
        '#ldap_certificate': 'CACERTIFICATE',

        """
        self.uri = config.get("LDAPURI")
        self.basedn = config.get("LDAPBASE")
        self.binddn = config.get("BINDDN")
        # object_classes is a comma separated list like
        # ["top", "person", "organizationalPerson", "user", "inetOrgPerson"]
        self.object_classes = [
            cl.strip() for cl in config.get("OBJECT_CLASSES", "").split(",")
        ]
        self.dn_template = config.get("DN_TEMPLATE", "")
        self.bindpw = config.get("BINDPW")
        self.timeout = float(config.get("TIMEOUT", 5))
        self.cache_timeout = int(config.get("CACHE_TIMEOUT", 120))
        self.sizelimit = int(config.get("SIZELIMIT", 500))
        self.loginname_attribute = config.get("LOGINNAMEATTRIBUTE",
                                              "").split(",")
        self.searchfilter = config.get("LDAPSEARCHFILTER")
        userinfo = config.get("USERINFO", "{}")
        self.userinfo = yaml.safe_load(userinfo)
        self.userinfo["username"] = self.loginname_attribute[0]
        multivalueattributes = config.get(
            "MULTIVALUEATTRIBUTES") or '["mobile"]'
        self.multivalueattributes = yaml.safe_load(multivalueattributes)
        self.map = yaml.safe_load(userinfo)
        self.uidtype = config.get("UIDTYPE", "DN")
        self.noreferrals = is_true(config.get("NOREFERRALS", False))
        self.start_tls = is_true(config.get("START_TLS", False))
        self.get_info = get_info_configuration(
            is_true(config.get("NOSCHEMAS", False)))
        self._editable = config.get("EDITABLE", False)
        self.scope = config.get("SCOPE") or ldap3.SUBTREE
        self.resolverId = self.uri
        self.authtype = config.get("AUTHTYPE", AUTHTYPE.SIMPLE)
        self.tls_verify = is_true(config.get("TLS_VERIFY", False))
        # Fallback to TLSv1. (int: 3, TLSv1.1: 4, v1.2: 5)
        self.tls_version = int(config.get("TLS_VERSION") or ssl.PROTOCOL_TLSv1)

        self.tls_ca_file = config.get("TLS_CA_FILE") or DEFAULT_CA_FILE
        if self.tls_verify and (self.uri.lower().startswith("ldaps")
                                or self.start_tls):
            self.tls_context = Tls(validate=ssl.CERT_REQUIRED,
                                   version=self.tls_version,
                                   ca_certs_file=self.tls_ca_file)
        else:
            self.tls_context = None
        self.serverpool_rounds = int(
            config.get("SERVERPOOL_ROUNDS") or SERVERPOOL_ROUNDS)
        self.serverpool_skip = int(
            config.get("SERVERPOOL_SKIP") or SERVERPOOL_SKIP)

        return self
예제 #37
0
    def _send_sms(self, message="<otp>"):
        """
        send sms

        :param message: the sms submit message - could contain placeholders
            like <otp> or <serial>
        :type message: string

        :return: submitted message
        :rtype: string
        """
        if is_true(self.get_tokeninfo("dynamic_phone")):
            phone = self.user.get_user_phone("mobile")
            if type(phone) == list and phone:
                # if there is a non-empty list, we use the first entry
                phone = phone[0]
        else:
            phone = self.get_tokeninfo("phone")
        if not phone:  # pragma: no cover
            log.warning("Token {0!s} does not have a phone number!".format(
                self.token.serial))
        otp = self.get_otp()[2]
        serial = self.get_serial()

        message = message.replace("<otp>", otp)
        message = message.replace("<serial>", serial)
        log.debug("sending SMS to phone number {0!s} ".format(phone))

        # First we try to get the new SMS gateway config style
        # The token specific identifier has priority over the system wide identifier
        sms_gateway_identifier = self.get_tokeninfo(
            "sms.identifier") or get_from_config("sms.identifier")

        if sms_gateway_identifier:
            # New style
            sms = create_sms_instance(sms_gateway_identifier)

        else:
            # Old style
            (SMSProvider, SMSProviderClass) = self._get_sms_provider()
            log.debug("smsprovider: {0!s}, class: {1!s}".format(
                SMSProvider, SMSProviderClass))

            try:
                sms = get_sms_provider_class(SMSProvider, SMSProviderClass)()
            except Exception as exc:
                log.error("Failed to load SMSProvider: {0!r}".format(exc))
                log.debug("{0!s}".format(traceback.format_exc()))
                raise exc

            try:
                # now we need the config from the env
                log.debug(
                    "loading SMS configuration for class {0!s}".format(sms))
                config = self._get_sms_provider_config()
                log.debug("config: {0!r}".format(config))
                sms.load_config(config)
            except Exception as exc:
                log.error(
                    "Failed to load sms.providerConfig: {0!r}".format(exc))
                log.debug("{0!s}".format(traceback.format_exc()))
                raise Exception(
                    "Failed to load sms.providerConfig: {0!r}".format(exc))

        log.debug("submitMessage: {0!r}, to phone {1!r}".format(
            message, phone))
        ret = sms.submit_message(phone, message)
        return ret, message
예제 #38
0
    def do(self, action, options=None):
        """
        This method executes the defined action in the given event.

        :param action:
        :param options: Contains the flask parameters g, request, response
            and the handler_def configuration
        :type options: dict
        :return:
        """
        ret = True
        g = options.get("g")
        request = options.get("request")
        response = options.get("response")
        content = json.loads(response.data)
        handler_def = options.get("handler_def")
        handler_options = handler_def.get("options", {})

        serial = request.all_data.get("serial") or \
                 content.get("detail", {}).get("serial") or \
                 g.audit_object.audit_data.get("serial")

        if action.lower() in [
                ACTION_TYPE.SET_TOKENREALM, ACTION_TYPE.SET_DESCRIPTION,
                ACTION_TYPE.DELETE, ACTION_TYPE.DISABLE, ACTION_TYPE.ENABLE,
                ACTION_TYPE.UNASSIGN, ACTION_TYPE.SET_VALIDITY,
                ACTION_TYPE.SET_COUNTWINDOW, ACTION_TYPE.SET_TOKENINFO
        ]:
            if serial:
                log.info("{0!s} for token {1!s}".format(action, serial))
                if action.lower() == ACTION_TYPE.SET_TOKENREALM:
                    realm = handler_options.get("realm")
                    only_realm = handler_options.get("only_realm")
                    # Set the realm..
                    log.info("Setting realm of token {0!s} to {1!s}".format(
                        serial, realm))
                    # Add the token realm
                    set_realms(serial, [realm], add=True)
                elif action.lower() == ACTION_TYPE.DELETE:
                    remove_token(serial=serial)
                elif action.lower() == ACTION_TYPE.DISABLE:
                    enable_token(serial, enable=False)
                elif action.lower() == ACTION_TYPE.ENABLE:
                    enable_token(serial, enable=True)
                elif action.lower() == ACTION_TYPE.UNASSIGN:
                    unassign_token(serial)
                elif action.lower() == ACTION_TYPE.SET_DESCRIPTION:
                    s_now = datetime.datetime.now().strftime(AUTH_DATE_FORMAT)
                    set_description(serial, (handler_options.get("description")
                                             or "").format(current_time=s_now))
                elif action.lower() == ACTION_TYPE.SET_COUNTWINDOW:
                    set_count_window(
                        serial, int(handler_options.get("count window", 50)))
                elif action.lower() == ACTION_TYPE.SET_TOKENINFO:
                    s_now = datetime.datetime.now().strftime(AUTH_DATE_FORMAT)
                    add_tokeninfo(serial, handler_options.get("key"),
                                  (handler_options.get("value")
                                   or "").format(current_time=s_now))
                elif action.lower() == ACTION_TYPE.SET_VALIDITY:
                    start_date = handler_options.get(VALIDITY.START)
                    end_date = handler_options.get(VALIDITY.END)
                    if start_date:
                        d = parse_date(start_date)
                        set_validity_period_start(serial, None,
                                                  d.strftime(DATE_FORMAT))
                    if end_date:
                        d = parse_date(end_date)
                        set_validity_period_end(serial, None,
                                                d.strftime(DATE_FORMAT))

            else:
                log.info("Action {0!s} requires serial number. But no serial "
                         "number could be found in request.")

        if action.lower() == ACTION_TYPE.INIT:
            log.info("Initializing new token")
            init_param = {
                "type": handler_options.get("tokentype"),
                "genkey": 1,
                "realm": handler_options.get("realm", "")
            }
            user = None
            if is_true(handler_options.get("user")):
                user = self._get_tokenowner(request)
                tokentype = handler_options.get("tokentype")
                # Some tokentypes need additional parameters or otherwise
                # will fail to enroll.
                # TODO: Other tokentypes will require additional parameters
                if tokentype == "sms":
                    init_param['phone'] = user.get_user_phone(
                        phone_type='mobile')
                    if not init_param['phone']:
                        log.warning("Enrolling SMS token. But the user "
                                    "{0!s} has no mobile number!".format(user))
                elif tokentype == "email":
                    init_param['email'] = user.info.get("email", "")
                    if not init_param['email']:
                        log.warning("Enrolling EMail token. But the user {0!s}"
                                    "has no email address!".format(user))
                elif tokentype == "motp":
                    init_param['motppin'] = handler_options.get("motppin")

            t = init_token(param=init_param, user=user)
            log.info("New token {0!s} enrolled.".format(t.token.serial))

        return ret
예제 #39
0
    def do(self, action, options=None):
        """
        This method executes the defined action in the given event.

        :param action:
        :param options: Contains the flask parameters g, request, response
            and the handler_def configuration
        :type options: dict
        :return:
        """
        ret = True
        response = options.get("response")
        try:
            content = self._get_response_content(response)
        except Exception:
            # The body could not be decoded to JSON. Actually this would be
            # a BadRequest, but we refrain from importing from werkzeug, here.
            log.info("The body could not be decoded to JSON.")
            # Early exist
            return ret
        handler_def = options.get("handler_def")
        handler_options = handler_def.get("options", {})

        json_pointer = handler_options.get("JSON pointer", "")
        value = handler_options.get("value")
        type = handler_options.get("type")
        comp = json_pointer.split("/")
        comp = [x for x in comp if x]
        if action.lower() == ACTION_TYPE.DELETE:
            try:
                if len(comp) == 1:
                    del (content[comp[0]])
                elif len(comp) == 2:
                    del (content[comp[0]][comp[1]])
                elif len(comp) == 3:
                    del (content[comp[0]][comp[1]][comp[2]])
                else:
                    log.warning(
                        "JSON pointer length of {0!s} not supported.".format(
                            len(comp)))
                options.get("response").data = json.dumps(content)
            except KeyError:
                log.warning(
                    "Can not delete response JSON Pointer {0!s}.".format(
                        json_pointer))
        elif action.lower() == ACTION_TYPE.SET:
            try:
                if type == "integer":
                    value = int(value)
                elif type == "bool":
                    value = is_true(value)
            except ValueError:
                log.warning("Failed to convert value")

            if len(comp) == 1:
                content[comp[0]] = value
            elif len(comp) == 2:
                content[comp[0]] = content.get(comp[0]) or {}
                content[comp[0]][comp[1]] = value
            elif len(comp) == 3:
                content[comp[0]] = content.get(comp[0]) or {}
                content[comp[0]][comp[1]] = content[comp[0]].get(comp[1]) or {}
                content[comp[0]][comp[1]][comp[2]] = value
            else:
                log.warning(
                    "JSON pointer of length {0!s} not supported.".format(
                        len(comp)))
            options.get("response").data = json.dumps(content)

        return ret
예제 #40
0
    def create_challenge(self, transactionid=None, options=None):
        """
        This method creates a challenge, which is submitted to the user.
        The submitted challenge will be preserved in the challenge
        database.

        If no transaction id is given, the system will create a transaction
        id and return it, so that the response can refer to this transaction.

        :param transactionid: the id of this challenge
        :param options: the request context parameters / data
        :type options: dict
        :return: tuple of (bool, message, transactionid, attributes)
        :rtype: tuple

        The return tuple builds up like this:
        ``bool`` if submit was successful;
        ``message`` which is displayed in the JSON response;
        additional ``attributes``, which are displayed in the JSON response.
        """
        options = options or {}
        message = get_action_values_from_options(SCOPE.AUTH,
                                                 ACTION.CHALLENGETEXT,
                                                 options) or DEFAULT_CHALLENGE_TEXT

        attributes = None
        data = None
        fb_identifier = self.get_tokeninfo(PUSH_ACTION.FIREBASE_CONFIG)
        if fb_identifier:
            challenge = b32encode_and_unicode(geturandom())
            fb_gateway = create_sms_instance(fb_identifier)
            pem_privkey = self.get_tokeninfo(PRIVATE_KEY_SERVER)
            smartphone_data = _build_smartphone_data(self.token.serial,
                                                     challenge, fb_gateway,
                                                     pem_privkey, options)
            res = fb_gateway.submit_message(self.get_tokeninfo("firebase_token"), smartphone_data)

            # Create the challenge in the challenge table if either the message
            # was successfully submitted to the Firebase API or if polling is
            # allowed in general or for this specific token.
            allow_polling = get_action_values_from_options(
                SCOPE.AUTH, PUSH_ACTION.ALLOW_POLLING, options=options) or PushAllowPolling.ALLOW
            if ((allow_polling == PushAllowPolling.ALLOW or
                 (allow_polling == PushAllowPolling.TOKEN and
                  is_true(self.get_tokeninfo(POLLING_ALLOWED, default='True')))) or res):
                validity = int(get_from_config('DefaultChallengeValidityTime', 120))
                tokentype = self.get_tokentype().lower()
                # Maybe there is a PushChallengeValidityTime...
                lookup_for = tokentype.capitalize() + 'ChallengeValidityTime'
                validity = int(get_from_config(lookup_for, validity))

                # Create the challenge in the database
                db_challenge = Challenge(self.token.serial,
                                         transaction_id=transactionid,
                                         challenge=challenge,
                                         data=data,
                                         session=options.get("session"),
                                         validitytime=validity)
                db_challenge.save()
                self.challenge_janitor()

            # If sending the Push message failed, we still raise an error.
            if not res:
                raise ValidateError("Failed to submit message to firebase service.")
        else:
            log.warning(u"The token {0!s} has no tokeninfo {1!s}. "
                        u"The message could not be sent.".format(self.token.serial,
                                                                 PUSH_ACTION.FIREBASE_CONFIG))
            raise ValidateError("The token has no tokeninfo. Can not send via firebase service.")

        return True, message, db_challenge.transaction_id, attributes
예제 #41
0
    def do(self, action, options=None):
        """
        This method executes the defined action in the given event.

        :param action:
        :param options: Contains the flask parameters g, request, response
            and the handler_def configuration
        :type options: dict
        :return:
        """
        ret = True
        g = options.get("g")
        request = options.get("request")
        response = options.get("response")
        content = self._get_response_content(response)
        handler_def = options.get("handler_def")
        handler_options = handler_def.get("options", {})
        notify_type = handler_options.get("To", NOTIFY_TYPE.TOKENOWNER)
        reply_to_type = handler_options.get("reply_to")
        try:
            logged_in_user = g.logged_in_user
        except Exception:
            logged_in_user = {}

        tokenowner = self._get_tokenowner(request)
        log.debug(u"Executing event for action {0!r}, user {1!r}, "
                  u"logged_in_user {2!r}".format(action, tokenowner,
                                                 logged_in_user))

        # Determine recipient
        recipient = None
        reply_to = None

        if reply_to_type == NOTIFY_TYPE.NO_REPLY_TO:
            reply_to = ""

        elif reply_to_type == NOTIFY_TYPE.TOKENOWNER and not tokenowner.is_empty():
            reply_to = tokenowner.info.get("email")

        elif reply_to_type == NOTIFY_TYPE.INTERNAL_ADMIN:
            username = handler_options.get("reply_to " + NOTIFY_TYPE.INTERNAL_ADMIN)
            internal_admin = get_db_admin(username)
            reply_to = internal_admin.email if internal_admin else ""

        elif reply_to_type == NOTIFY_TYPE.ADMIN_REALM:
            # Adds all email addresses from a specific admin realm to the reply-to-header
            admin_realm = handler_options.get("reply_to " + NOTIFY_TYPE.ADMIN_REALM)
            attr = is_attribute_at_all()
            ulist = get_user_list({"realm": admin_realm}, custom_attributes=attr)
            # create a list of all user-emails, if the user has an email
            emails = [u.get("email") for u in ulist if u.get("email")]
            reply_to = ",".join(emails)

        elif reply_to_type == NOTIFY_TYPE.LOGGED_IN_USER:
            # Add email address from the logged in user into the reply-to header
            if logged_in_user.get("username") and not logged_in_user.get(
                    "realm"):
                # internal admins have no realm
                internal_admin = get_db_admin(logged_in_user.get("username"))
                if internal_admin:
                    reply_to = internal_admin.email if internal_admin else ""

            else:
                # Try to find the user in the specified realm
                user_obj = User(logged_in_user.get("username"),
                                logged_in_user.get("realm"))
                if user_obj:
                    reply_to = user_obj.info.get("email") if user_obj else ""

        elif reply_to_type == NOTIFY_TYPE.EMAIL:
            email = handler_options.get("reply_to " + NOTIFY_TYPE.EMAIL, "").split(",")
            reply_to = email[0]

        else:
            log.warning("Was not able to determine the email for the reply-to "
                        "header: {0!s}".format(handler_def))

        if notify_type == NOTIFY_TYPE.TOKENOWNER and not tokenowner.is_empty():
            recipient = {
                "givenname": tokenowner.info.get("givenname"),
                "surname": tokenowner.info.get("surname"),
                "username": tokenowner.login,
                "userrealm": tokenowner.realm,
                "email": tokenowner.info.get("email"),
                "mobile": tokenowner.info.get("mobile")
            }
        elif notify_type == NOTIFY_TYPE.INTERNAL_ADMIN:
            username = handler_options.get("To " + NOTIFY_TYPE.INTERNAL_ADMIN)
            internal_admin = get_db_admin(username)
            recipient = {
                "givenname": username,
                "email": internal_admin.email if internal_admin else ""
            }
        elif notify_type == NOTIFY_TYPE.ADMIN_REALM:
            # Send emails to all the users in the specified admin realm
            admin_realm = handler_options.get("To " + NOTIFY_TYPE.ADMIN_REALM)
            attr = is_attribute_at_all()
            ulist = get_user_list({"realm": admin_realm}, custom_attributes=attr)
            # create a list of all user-emails, if the user has an email
            emails = [u.get("email") for u in ulist if u.get("email")]
            recipient = {
                "givenname": "admin of realm {0!s}".format(admin_realm),
                "email": emails
            }
        elif notify_type == NOTIFY_TYPE.LOGGED_IN_USER:
            # Send notification to the logged in user
            if logged_in_user.get("username") and not logged_in_user.get(
                    "realm"):
                # internal admins have no realm
                internal_admin = get_db_admin(logged_in_user.get("username"))
                if internal_admin:
                    recipient = {
                        "givenname": logged_in_user.get("username"),
                        "email": internal_admin.email if internal_admin else ""
                    }
            else:
                # Try to find the user in the specified realm
                user_obj = User(logged_in_user.get("username"),
                                logged_in_user.get("realm"))
                if user_obj:
                    recipient = {
                        "givenname": user_obj.info.get("givenname"),
                        "surname": user_obj.info.get("surname"),
                        "email": user_obj.info.get("email"),
                        "mobile": user_obj.info.get("mobile")
                    }

        elif notify_type == NOTIFY_TYPE.EMAIL:
            email = handler_options.get("To " + NOTIFY_TYPE.EMAIL, "").split(",")
            recipient = {
                "email": email
            }
        else:
            log.warning("Was not able to determine the recipient for the user "
                        "notification: {0!s}".format(handler_def))

        if recipient or action.lower() == "savefile":
            # In case of "savefile" we do not need a recipient
            # Collect all data
            body = handler_options.get("body") or DEFAULT_BODY
            if body.startswith("file:"):  # pragma no cover
                # We read the template from the file.
                filename = body[5:]
                try:
                    with open(filename, "r", encoding="utf-8") as f:
                        body = f.read()
                except Exception as e:
                    log.warning(u"Failed to read email template from file {0!r}: {1!r}".format(filename, e))
                    log.debug(u"{0!s}".format(traceback.format_exc()))

            subject = handler_options.get("subject") or \
                      "An action was performed on your token."
            serial = request.all_data.get("serial") or \
                     content.get("detail", {}).get("serial") or \
                     g.audit_object.audit_data.get("serial")
            registrationcode = content.get("detail", {}).get("registrationcode")
            pin = content.get("detail", {}).get("pin")
            googleurl_value = content.get("detail", {}).get("googleurl",
                                                            {}).get("value")
            googleurl_img = content.get("detail", {}).get("googleurl",
                                                          {}).get("img")
            tokentype = None
            if serial:
                tokens = get_tokens(serial=serial)
                if tokens:
                    tokentype = tokens[0].get_tokentype()
            else:
                token_objects = get_tokens(user=tokenowner)
                serial = ','.join([tok.get_serial() for tok in token_objects])

            tags = create_tag_dict(logged_in_user=logged_in_user,
                                   request=request,
                                   client_ip=g.client_ip,
                                   pin=pin,
                                   googleurl_value=googleurl_value,
                                   recipient=recipient,
                                   tokenowner=tokenowner,
                                   serial=serial,
                                   tokentype=tokentype,
                                   registrationcode=registrationcode,
                                   escape_html=action.lower() == "sendmail" and
                                               handler_options.get("mimetype", "").lower() == "html")

            body = to_unicode(body).format(googleurl_img=googleurl_img, **tags)
            subject = subject.format(**tags)
            # Send notification
            if action.lower() == "sendmail":
                emailconfig = handler_options.get("emailconfig")
                mimetype = handler_options.get("mimetype", "plain")
                useremail = recipient.get("email")
                attach_qrcode = is_true(handler_options.get("attach_qrcode"))

                if attach_qrcode and googleurl_img:
                    # get the image part of the googleurl
                    googleurl = urlopen(googleurl_img)
                    mail_body = MIMEMultipart('related')
                    mail_body.attach(MIMEText(body, mimetype))
                    mail_img = MIMEImage(googleurl.read())
                    mail_img.add_header('Content-ID', '<token_image>')
                    mail_img.add_header('Content-Disposition',
                                        'inline; filename="{0!s}.png"'.format(serial))
                    mail_body.attach(mail_img)
                    body = mail_body
                try:
                    ret = send_email_identifier(emailconfig,
                                                recipient=useremail,
                                                subject=subject, body=body,
                                                reply_to=reply_to,
                                                mimetype=mimetype)
                except Exception as exx:
                    log.error("Failed to send email: {0!s}".format(exx))
                    ret = False
                if ret:
                    log.info("Sent a notification email to user {0}".format(
                        recipient))
                else:
                    log.warning("Failed to send a notification email to user "
                                "{0}".format(recipient))

            elif action.lower() == "savefile":
                spooldir = get_app_config_value("PI_NOTIFICATION_HANDLER_SPOOLDIRECTORY",
                                                "/var/lib/privacyidea/notifications/")
                filename = handler_options.get("filename")
                random = get_alphanum_str(16)
                filename = filename.format(random=random, **tags).lstrip(os.path.sep)
                outfile = os.path.normpath(os.path.join(spooldir, filename))
                if not outfile.startswith(spooldir):
                    log.error(u'Cannot write outside of spooldir {0!s}!'.format(spooldir))
                else:
                    try:
                        with open(outfile, "w") as f:
                            f.write(body)
                    except Exception as err:
                        log.error(u"Failed to write notification file: {0!s}".format(err))

            elif action.lower() == "sendsms":
                smsconfig = handler_options.get("smsconfig")
                userphone = recipient.get("mobile")
                try:
                    ret = send_sms_identifier(smsconfig, userphone, body)
                except Exception as exx:
                    log.error("Failed to send sms: {0!s}".format(exx))
                    ret = False
                if ret:
                    log.info("Sent a notification sms to user {0}".format(
                        recipient))
                else:
                    log.warning("Failed to send a notification email to user "
                                "{0}".format(recipient))

        return ret
예제 #42
0
    def _api_endpoint_get(cls, g, request_data):
        """ Handle all GET requests to the api endpoint.

        Currently this is only used for polling.
        :param g: The Flask context
        :param request_data: Dictionary containing the parameters of the request
        :type request_data: dict
        :returns: Result of the polling operation, 'True' if an unanswered and
                  matching challenge exists, 'False' otherwise.
        :rtype: bool
        """
        # By default we allow polling if the policy is not set.
        allow_polling = get_action_values_from_options(
            SCOPE.AUTH, PUSH_ACTION.ALLOW_POLLING,
            options={'g': g}) or PushAllowPolling.ALLOW
        if allow_polling == PushAllowPolling.DENY:
            raise PolicyError('Polling not allowed!')
        serial = getParam(request_data, "serial", optional=False)
        timestamp = getParam(request_data, 'timestamp', optional=False)
        signature = getParam(request_data, 'signature', optional=False)
        # first check if the timestamp is in the required span
        cls._check_timestamp_in_range(timestamp, POLL_TIME_WINDOW)
        # now check the signature
        # first get the token
        try:
            tok = get_one_token(serial=serial, tokentype=cls.get_class_type())
            # If the push_allow_polling policy is set to "token" we also
            # need to check the POLLING_ALLOWED tokeninfo. If it evaluated
            # to 'False', polling is not allowed for this token. If the
            # tokeninfo value evaluates to 'True' or is not set at all,
            # polling is allowed for this token.
            if allow_polling == PushAllowPolling.TOKEN:
                if not is_true(tok.get_tokeninfo(POLLING_ALLOWED, default='True')):
                    log.debug('Polling not allowed for pushtoken {0!s} due to '
                              'tokeninfo.'.format(serial))
                    raise PolicyError('Polling not allowed!')

            pubkey_obj = _build_verify_object(tok.get_tokeninfo(PUBLIC_KEY_SMARTPHONE))
            sign_data = u"{serial}|{timestamp}".format(**request_data)
            pubkey_obj.verify(b32decode(signature),
                              sign_data.encode("utf8"),
                              padding.PKCS1v15(),
                              hashes.SHA256())
            # The signature was valid now check for an open challenge
            # we need the private server key to sign the smartphone data
            pem_privkey = tok.get_tokeninfo(PRIVATE_KEY_SERVER)
            # we also need the FirebaseGateway for this token
            fb_identifier = tok.get_tokeninfo(PUSH_ACTION.FIREBASE_CONFIG)
            if not fb_identifier:
                raise ResourceNotFoundError('The pushtoken {0!s} has no Firebase configuration '
                                            'assigned.'.format(serial))
            fb_gateway = create_sms_instance(fb_identifier)
            options = {'g': g}
            challenges = []
            challengeobject_list = get_challenges(serial=serial)
            for chal in challengeobject_list:
                # check if the challenge is active and not already answered
                _cnt, answered = chal.get_otp_status()
                if not answered and chal.is_valid():
                    # then return the necessary smartphone data to answer
                    # the challenge
                    sp_data = _build_smartphone_data(serial, chal.challenge,
                                                     fb_gateway, pem_privkey, options)
                    challenges.append(sp_data)
            # return the challenges as a list in the result value
            result = challenges
        except (ResourceNotFoundError, ParameterError,
                InvalidSignature, ConfigAdminError, BinasciiError) as e:
            # to avoid disclosing information we always fail with an invalid
            # signature error even if the token with the serial could not be found
            log.debug('{0!s}'.format(traceback.format_exc()))
            log.info('The following error occurred during the signature '
                     'check: "{0!r}"'.format(e))
            raise privacyIDEAError('Could not verify signature!')

        return result
예제 #43
0
def get_policy(name=None, export=None):
    """
    this function is used to retrieve the policies that you
    defined.
    It can also be used to export the policy to a file.

    :query name: will only return the policy with the given name
    :query export: The filename needs to be specified as the
        third part of the URL like policy.cfg. It
        will then be exported to this file.
    :query realm: will return all policies in the given realm
    :query scope: will only return the policies within the given scope
    :query active: Set to true or false if you only want to display
        active or inactive policies.

    :return: a json result with the configuration of the specified policies
    :rtype: json

    :status 200: Policy created or modified.
    :status 401: Authentication failed

    **Example request**:

    In this example a policy "pol1" is created.

    .. sourcecode:: http

       GET /policy/pol1 HTTP/1.1
       Host: example.com
       Accept: application/json

    **Example response**:

    .. sourcecode:: http

       HTTP/1.0 200 OK
       Content-Type: application/json

        {
          "id": 1,
          "jsonrpc": "2.0",
          "result": {
            "status": true,
            "value": {
              "pol_update_del": {
                "action": "enroll",
                "active": true,
                "client": "1.1.1.1",
                "name": "pol_update_del",
                "realm": "r1",
                "resolver": "test",
                "scope": "selfservice",
                "time": "",
                "user": "******"
              }
            }
          },
          "version": "privacyIDEA unknown"
        }
    """
    param = getLowerParams(request.all_data)
    realm = getParam(param, "realm")
    scope = getParam(param, "scope")
    active = getParam(param, "active")
    if active is not None:
        active = is_true(active)

    P = g.policy_object
    if not export:
        log.debug(
            "retrieving policy name: {0!s}, realm: {1!s}, scope: {2!s}".format(
                name, realm, scope))

        pol = P.list_policies(name=name,
                              realm=realm,
                              scope=scope,
                              active=active)
        ret = send_result(pol)
    else:
        # We want to export all policies
        pol = P.list_policies()
        ret = send_file(export_policies(pol),
                        export,
                        content_type='text/plain')

    g.audit_object.log({
        "success":
        True,
        'info':
        u"name = {0!s}, realm = {1!s}, scope = {2!s}".format(
            name, realm, scope)
    })
    return ret
예제 #44
0
    def do(self, action, options=None):
        """
        This method executes the defined action in the given event.

        :param action:
        :param options: Contains the flask parameters g, request, response
            and the handler_def configuration
        :type options: dict
        :return:
        """
        ret = True
        g = options.get("g")
        request = options.get("request")
        response = options.get("response")
        content = json.loads(response.data)
        handler_def = options.get("handler_def")
        handler_options = handler_def.get("options", {})

        serial = request.all_data.get("serial") or \
                 content.get("detail", {}).get("serial") or \
                 g.audit_object.audit_data.get("serial")

        if action.lower() in [
                ACTION_TYPE.SET_TOKENREALM, ACTION_TYPE.SET_DESCRIPTION,
                ACTION_TYPE.DELETE, ACTION_TYPE.DISABLE, ACTION_TYPE.ENABLE,
                ACTION_TYPE.UNASSIGN, ACTION_TYPE.SET_VALIDITY,
                ACTION_TYPE.SET_COUNTWINDOW, ACTION_TYPE.SET_TOKENINFO,
                ACTION_TYPE.SET_FAILCOUNTER, ACTION_TYPE.DELETE_TOKENINFO
        ]:
            if serial:
                log.info("{0!s} for token {1!s}".format(action, serial))
                if action.lower() == ACTION_TYPE.SET_TOKENREALM:
                    realm = handler_options.get("realm")
                    only_realm = is_true(handler_options.get("only_realm"))
                    # Set the realm..
                    log.info("Setting realm of token {0!s} to {1!s}".format(
                        serial, realm))
                    # Add the token realm
                    set_realms(serial, [realm], add=not only_realm)
                elif action.lower() == ACTION_TYPE.DELETE:
                    remove_token(serial=serial)
                elif action.lower() == ACTION_TYPE.DISABLE:
                    enable_token(serial, enable=False)
                elif action.lower() == ACTION_TYPE.ENABLE:
                    enable_token(serial, enable=True)
                elif action.lower() == ACTION_TYPE.UNASSIGN:
                    unassign_token(serial)
                elif action.lower() == ACTION_TYPE.SET_DESCRIPTION:
                    description = handler_options.get("description") or ""
                    description, td = parse_time_offset_from_now(description)
                    s_now = (datetime.datetime.now(tzlocal()) +
                             td).strftime(AUTH_DATE_FORMAT)
                    set_description(
                        serial,
                        description.format(
                            current_time=s_now,
                            now=s_now,
                            client_ip=g.client_ip,
                            ua_browser=request.user_agent.browser,
                            ua_string=request.user_agent.string))
                elif action.lower() == ACTION_TYPE.SET_COUNTWINDOW:
                    set_count_window(
                        serial, int(handler_options.get("count window", 50)))
                elif action.lower() == ACTION_TYPE.SET_TOKENINFO:
                    tokeninfo = handler_options.get("value") or ""
                    tokeninfo, td = parse_time_offset_from_now(tokeninfo)
                    s_now = (datetime.datetime.now(tzlocal()) +
                             td).strftime(AUTH_DATE_FORMAT)
                    try:
                        username = request.User.loginname
                        realm = request.User.realm
                    except Exception:
                        username = "******"
                        realm = "N/A"
                    add_tokeninfo(
                        serial, handler_options.get("key"),
                        tokeninfo.format(current_time=s_now,
                                         now=s_now,
                                         client_ip=g.client_ip,
                                         username=username,
                                         realm=realm,
                                         ua_browser=request.user_agent.browser,
                                         ua_string=request.user_agent.string))
                elif action.lower() == ACTION_TYPE.DELETE_TOKENINFO:
                    delete_tokeninfo(serial, handler_options.get("key"))
                elif action.lower() == ACTION_TYPE.SET_VALIDITY:
                    start_date = handler_options.get(VALIDITY.START)
                    end_date = handler_options.get(VALIDITY.END)
                    if start_date:
                        d = parse_date(start_date)
                        set_validity_period_start(serial, None,
                                                  d.strftime(DATE_FORMAT))
                    if end_date:
                        d = parse_date(end_date)
                        set_validity_period_end(serial, None,
                                                d.strftime(DATE_FORMAT))
                elif action.lower() == ACTION_TYPE.SET_FAILCOUNTER:
                    try:
                        set_failcounter(
                            serial, int(handler_options.get("fail counter")))
                    except Exception as exx:
                        log.warning("Misconfiguration: Failed to set fail "
                                    "counter!")
            else:
                log.info("Action {0!s} requires serial number. But no serial "
                         "number could be found in request.")

        if action.lower() == ACTION_TYPE.INIT:
            log.info("Initializing new token")
            init_param = {
                "type": handler_options.get("tokentype"),
                "genkey": 1,
                "realm": handler_options.get("realm", "")
            }
            user = None
            if is_true(handler_options.get("user")):
                user = self._get_tokenowner(request)
                tokentype = handler_options.get("tokentype")
                # Some tokentypes need additional parameters
                if handler_options.get("additional_params"):
                    add_params = yaml.safe_load(
                        handler_options.get("additional_params"))
                    if type(add_params) == dict:
                        init_param.update(add_params)

                if tokentype == "sms":
                    if handler_options.get("dynamic_phone"):
                        init_param["dynamic_phone"] = 1
                    else:
                        init_param['phone'] = user.get_user_phone(
                            phone_type='mobile')
                        if not init_param['phone']:
                            log.warning(
                                "Enrolling SMS token. But the user "
                                "{0!r} has no mobile number!".format(user))
                elif tokentype == "email":
                    if handler_options.get("dynamic_email"):
                        init_param["dynamic_email"] = 1
                    else:
                        init_param['email'] = user.info.get("email", "")
                        if not init_param['email']:
                            log.warning(
                                "Enrolling EMail token. But the user {0!s}"
                                "has no email address!".format(user))
                elif tokentype == "motp":
                    init_param['motppin'] = handler_options.get("motppin")

            t = init_token(param=init_param, user=user)
            log.info("New token {0!s} enrolled.".format(t.token.serial))

        return ret
예제 #45
0
    def testconnection(cls, param):
        """
        This function lets you test the to be saved LDAP connection.

        

        :param param: A dictionary with all necessary parameter to test
                        the connection.
        :type param: dict

        :return: Tuple of success and a description
        :rtype: (bool, string)

        Parameters are:
            BINDDN, BINDPW, LDAPURI, TIMEOUT, LDAPBASE, LOGINNAMEATTRIBUTE,
            LDAPSEARCHFILTER, USERINFO, SIZELIMIT, NOREFERRALS, CACERTIFICATE,
            AUTHTYPE, TLS_VERIFY, TLS_CA_FILE
        """
        success = False
        uidtype = param.get("UIDTYPE")
        timeout = float(param.get("TIMEOUT", 5))
        ldap_uri = param.get("LDAPURI")
        size_limit = int(param.get("SIZELIMIT", 500))
        if is_true(param.get("TLS_VERIFY")) \
                and (ldap_uri.lower().startswith("ldaps") or
                                    param.get("START_TLS")):
            tls_ca_file = param.get("TLS_CA_FILE") or DEFAULT_CA_FILE
            tls_context = Tls(validate=ssl.CERT_REQUIRED,
                              version=ssl.PROTOCOL_TLSv1,
                              ca_certs_file=tls_ca_file)
        else:
            tls_context = None
        get_info = get_info_configuration(is_true(param.get("NOSCHEMAS")))
        try:
            server_pool = cls.get_serverpool(ldap_uri, timeout,
                                             tls_context=tls_context,
                                             get_info=get_info)
            l = cls.create_connection(authtype=param.get("AUTHTYPE",
                                                          AUTHTYPE.SIMPLE),
                                      server=server_pool,
                                      user=param.get("BINDDN"),
                                      password=param.get("BINDPW"),
                                      receive_timeout=timeout,
                                      auto_referrals=not param.get(
                                           "NOREFERRALS"),
                                      start_tls=param.get("START_TLS", False))
            #log.error("LDAP Server Pool States: %s" % server_pool.pool_states)
            if not l.bind():
                raise Exception("Wrong credentials")
            # create searchattributes
            attributes = yaml.safe_load(param["USERINFO"]).values()
            if uidtype.lower() != "dn":
                attributes.append(str(uidtype))
            # search for users...
            g = l.extend.standard.paged_search(
                search_base=param["LDAPBASE"],
                search_filter="(&" + param["LDAPSEARCHFILTER"] + ")",
                search_scope=param.get("SCOPE") or ldap3.SUBTREE,
                attributes=attributes,
                paged_size=100,
                size_limit=size_limit,
                generator=True)
            # returns a generator of dictionaries
            count = 0
            uidtype_count = 0
            for entry in g:
                try:
                    userid = cls._get_uid(entry, uidtype)
                    count += 1
                    if userid:
                        uidtype_count += 1
                except Exception as exx:  # pragma: no cover
                    log.warning("Error during fetching LDAP objects:"
                                " {0!r}".format(exx))
                    log.debug("{0!s}".format(traceback.format_exc()))

            if uidtype_count < count:  # pragma: no cover
                desc = _("Your LDAP config found %i user objects, but only %i "
                         "with the specified uidtype" % (count, uidtype_count))
            else:
                desc = _("Your LDAP config seems to be OK, %i user objects "
                         "found.") % count

            l.unbind()
            success = True

        except Exception as e:
            desc = "{0!r}".format(e)

        return success, desc
예제 #46
0
    def create_challenge(self, transactionid=None, options=None):
        """
        create a challenge, which is submitted to the user

        :param transactionid: the id of this challenge
        :param options: the request context parameters / data
                You can pass exception=1 to raise an exception, if
                the SMS could not be sent. Otherwise the message is contained in the response.
        :return: tuple of (bool, message and data)
                 bool, if submit was successful
                 message is submitted to the user
                 data is preserved in the challenge
                 attributes - additional attributes, which are displayed in the
                    output
        """
        success = False
        options = options or {}
        return_message = get_action_values_from_options(
            SCOPE.AUTH, "{0!s}_{1!s}".format(self.get_class_type(),
                                             ACTION.CHALLENGETEXT),
            options) or _("Enter the OTP from the Email:")
        attributes = {'state': transactionid}
        validity = int(get_from_config("email.validtime", 120))

        if self.is_active() is True:
            counter = self.get_otp_count()
            log.debug("counter={0!r}".format(counter))
            self.inc_otp_counter(counter, reset=False)
            # At this point we must not bail out in case of an
            # Gateway error, since checkPIN is successful. A bail
            # out would cancel the checking of the other tokens
            try:
                message_template, mimetype = self._get_email_text_or_subject(
                    options)
                subject_template, _n = self._get_email_text_or_subject(
                    options, EMAILACTION.EMAILSUBJECT, "Your OTP")

                # Create the challenge in the database
                if is_true(get_from_config("email.concurrent_challenges")):
                    data = self.get_otp()[2]
                else:
                    data = None
                db_challenge = Challenge(self.token.serial,
                                         transaction_id=transactionid,
                                         challenge=options.get("challenge"),
                                         data=data,
                                         session=options.get("session"),
                                         validitytime=validity)
                db_challenge.save()
                transactionid = transactionid or db_challenge.transaction_id
                # We send the email after creating the challenge for testing.
                success, sent_message = self._compose_email(
                    message=message_template,
                    subject=subject_template,
                    mimetype=mimetype)

            except Exception as e:
                info = ("The PIN was correct, but the "
                        "EMail could not be sent: %r" % e)
                log.warning(info)
                log.debug(u"{0!s}".format(traceback.format_exc()))
                return_message = info
                if is_true(options.get("exception")):
                    raise Exception(info)

        expiry_date = datetime.datetime.now() + \
                                    datetime.timedelta(seconds=validity)
        attributes['valid_until'] = "{0!s}".format(expiry_date)

        return success, return_message, transactionid, attributes