Exemplo n.º 1
0
    def update(self, param, reset_failcount=True):
        """
        This is called during initialzaton of the token
        to add additional attributes to the token object.

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

        :return: nothing
        """
        HotpTokenClass.update(self, param, reset_failcount=reset_failcount)

        timeStep = param.get("timeStep",
                             get_from_config("totp.timeStep") or 30)

        timeWindow = param.get("timeWindow",
                               get_from_config("totp.timeWindow") or 180)

        timeShift = param.get("timeShift",
                              get_from_config("totp.timeShift") or 0)
        # we support various hashlib methods, but only on create
        # which is effectively set in the update
        hashlibStr = param.get("totp.hashlib",
                               get_from_config("totp.hashlib",
                                               u'sha1'))

        self.add_tokeninfo("timeWindow", timeWindow)
        self.add_tokeninfo("timeShift", timeShift)
        self.add_tokeninfo("timeStep", timeStep)
        self.add_tokeninfo("hashlib", hashlibStr)
Exemplo n.º 2
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 = 'Please scan the QR Code'

        # Get ValidityTime=120s. Maybe there is a TIQRChallengeValidityTime...
        validity = int(get_from_config('DefaultChallengeValidityTime', 120))
        tokentype = self.get_tokentype().lower()
        lookup_for = tokentype.capitalize() + 'ChallengeValidityTime'
        validity = int(get_from_config(lookup_for, validity))

        # We need to set the user ID
        user_identifier, user_displayname = self.get_user_displayname()

        service_identifier = get_from_config("tiqr.serviceIdentifier") or \
                             "org.privacyidea"

        # Get the OCRASUITE from the token information
        ocrasuite = self.get_tokeninfo("ocrasuite") or OCRA_DEFAULT_SUITE
        # Depending on the OCRA-SUITE we create the challenge
        os = OCRASuite(ocrasuite)
        challenge = os.create_challenge()

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

        authurl = "tiqrauth://%s@%s/%s/%s" % (user_identifier,
                                              service_identifier,
                                              db_challenge.transaction_id,
                                              challenge)
        attributes = {"img": create_img(authurl, width=250),
                      "value": authurl,
                      "poll": True,
                      "hideResponseInput": True}

        return True, message, db_challenge.transaction_id, attributes
Exemplo n.º 3
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,
                                                 "{0!s}_{1!s}".format(self.get_class_type(),
                                                                      ACTION.CHALLENGETEXT),
                                                 options)or _(u'Please confirm with your U2F token ({0!s})').format(
            self.token.description)

        validity = int(get_from_config('DefaultChallengeValidityTime', 120))
        tokentype = self.get_tokentype().lower()
        lookup_for = tokentype.capitalize() + 'ChallengeValidityTime'
        validity = int(get_from_config(lookup_for, validity))

        challenge = geturandom(32)
        # Create the challenge in the database
        db_challenge = Challenge(self.token.serial,
                                 transaction_id=transactionid,
                                 challenge=hexlify_and_unicode(challenge),
                                 data=None,
                                 session=options.get("session"),
                                 validitytime=validity)
        db_challenge.save()
        sec_object = self.token.get_otpkey()
        key_handle_hex = sec_object.getKey()
        key_handle_bin = binascii.unhexlify(key_handle_hex)
        key_handle_url = url_encode(key_handle_bin)
        challenge_url = url_encode(challenge)
        u2f_sign_request = {"appId": self.get_tokeninfo("appId"),
                            "version": U2F_Version,
                            "challenge": challenge_url,
                            "keyHandle": key_handle_url}

        image_url = IMAGES.get(self.token.description.lower().split()[0], "")
        response_details = {"u2fSignRequest": u2f_sign_request,
                            "hideResponseInput": True,
                            "img": image_url}

        return True, message, db_challenge.transaction_id, response_details
Exemplo n.º 4
0
    def test_04_store_encrypted_values(self):
        r = set_privacyidea_config("mySecretData", "soho",
                                   typ="password", desc="Very important")
        self.assertTrue(r == "insert", r)

        r = get_from_config("mySecretData")
        self.assertTrue(r == "soho", r)

        r = get_from_config()
        self.assertTrue(r.get("mySecretData") == "soho",
                        r.get("mySecretData"))
Exemplo n.º 5
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.

        The challenge is a randomly selected question of the available
        questions for this token.

        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 {}

        # Get a random question
        questions = []
        tinfo = self.get_tokeninfo()
        for question, answer in tinfo.iteritems():
            if question.endswith(".type") and answer == "password":
                # This is "Question1?.type" of type "password"
                # So this is actually a question and we add the question to
                # the list
                questions.append(question.strip(".type"))
        message = random.choice(questions)
        attributes = None

        validity = int(get_from_config('DefaultChallengeValidityTime', 120))
        tokentype = self.get_tokentype().lower()
        # Maybe there is a QUESTIONChallengeValidityTime...
        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=message,
                                 validitytime=validity)
        db_challenge.save()
        self.challenge_janitor()
        return True, message, db_challenge.transaction_id, attributes
Exemplo n.º 6
0
    def request(config, user, password):
        """
        Perform a RADIUS request to a RADIUS server.
        The RADIUS configuration contains the IP address, the port and the
        secret of the RADIUS server.

        * config.server
        * config.port
        * config.secret

        :param config: The RADIUS configuration
        :type config: RADIUSServer Database Model
        :param user: the radius username
        :param password: the radius password
        :return: True or False. If any error occurs, an exception is raised.
        """
        success = False

        nas_identifier = get_from_config("radius.nas_identifier",
                                         "privacyIDEA")
        r_dict = config.dictionary or get_from_config("radius.dictfile",
                                                      "/etc/privacyidea/"
                                                      "dictionary")
        log.debug("NAS Identifier: %r, "
                  "Dictionary: %r" % (nas_identifier, r_dict))
        log.debug("constructing client object "
                  "with server: %r, port: %r, secret: %r" %
                  (config.server, config.port, config.secret))

        srv = Client(server=config.server,
                     authport=config.port,
                     secret=decryptPassword(config.secret),
                     dict=Dictionary(r_dict))

        req = srv.CreateAuthPacket(code=pyrad.packet.AccessRequest,
                                   User_Name=user.encode('ascii'),
                                   NAS_Identifier=nas_identifier.encode('ascii'))

        req["User-Password"] = req.PwCrypt(password)
        response = srv.SendPacket(req)
        if response.code == pyrad.packet.AccessAccept:
            log.info("Radiusserver %s granted "
                     "access to user %s." % (config.server, user))
            success = True
        else:
            log.warning("Radiusserver %s"
                        "rejected access to user %s." %
                        (config.server, user))

        return success
Exemplo n.º 7
0
    def get_init_detail(self, params=None, user=None):
        """
        At the end of the initialization we ask the user to press the button
        """
        response_detail = {}
        if self.init_step == 1:
            # This is the first step of the init request
            app_id = get_from_config("u2f.appId", "").strip("/")
            from privacyidea.lib.error import TokenAdminError
            if not app_id:
                raise TokenAdminError(_("You need to define the appId in the "
                                        "token config!"))
            nonce = urlsafe_b64encode_and_unicode(geturandom(32))
            response_detail = TokenClass.get_init_detail(self, params, user)
            register_request = {"version": U2F_Version,
                                "challenge": nonce,
                                "appId": app_id}
            response_detail["u2fRegisterRequest"] = register_request
            self.add_tokeninfo("appId", app_id)

        elif self.init_step == 2:
            # This is the second step of the init request
            response_detail["u2fRegisterResponse"] = {"subject":
                                                          self.token.description}

        return response_detail
Exemplo n.º 8
0
    def api_endpoint(request, g):
        """
        This provides a function to be plugged into the API endpoint
        /ttype/u2f

        The u2f token can return the facet list at this URL.

        :param request: The Flask request
        :param g: The Flask global object g
        :return: Flask Response or text
        """
        app_id = get_from_config("u2f.appId").strip("/")

        # Read the facets from the policies
        pol_facets = g.policy_object.get_action_values(U2FACTION.FACETS,
                                                       scope=SCOPE.AUTH,
                                                       client=request.remote_addr)
        facet_list = ["https://%s" % x for x in pol_facets]
        facet_list.append(app_id)

        log.debug("Sending facets lists for appId %s: %s" % (app_id,
                                                             facet_list))
        res = {"trustedFacets": [{"version": {"major": 1,
                                              "minor": 0},
                                  "ids": facet_list
                                  }
                                 ]
               }
        return "json", res
Exemplo n.º 9
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
Exemplo n.º 10
0
 def __init__(self, aToken):
     HotpTokenClass.__init__(self, aToken)
     self.set_type(u"email")
     self.mode = ['challenge']
     # we support various hashlib methods, but only on create
     # which is effectively set in the update
     self.hashlibStr = get_from_config("hotp.hashlib", "sha1")
Exemplo n.º 11
0
def is_remote_user_allowed(req):
    """
    Checks if the REMOTE_USER server variable is allowed to be used.

    .. note:: This is not used as a decorator!

    :param req: The flask request, containing the remote user and the client IP
    :return:
    """
    res = False
    if req.remote_user:
        loginname, realm = split_user(req.remote_user)
        realm = realm or get_default_realm()

        # Check if the remote user is allowed
        if "client_ip" not in g:
            g.client_ip = get_client_ip(req,
                                        get_from_config(SYSCONF.OVERRIDECLIENT))
        if "policy_object" not in g:
            g.policy_object = PolicyClass()
        ruser_active = g.policy_object.get_action_values(ACTION.REMOTE_USER,
                                                         scope=SCOPE.WEBUI,
                                                         user=loginname,
                                                         realm=realm,
                                                         client=g.client_ip)

        res = ruser_active

    return res
Exemplo n.º 12
0
    def check_otp_exist(self, otp, window=None, options=None, symetric=True,
                        inc_counter=True):
        """
        checks if the given OTP value is/are values of this very token at all.
        This is used to autoassign and to determine the serial number of
        a token.
        In fact it is a check_otp with an enhanced window.

        :param otp: the to be verified otp value
        :type otp: string
        :param window: the lookahead window for the counter in seconds!!!
        :type window: int
        :return: counter or -1 if otp does not exist
        :rtype:  int
        """
        options = options or {}
        timeStepping = int(self.get_tokeninfo("timeStep") or
                           get_from_config("totp.timeStep") or 30)
        window = window or (self.get_sync_window() * timeStepping)
        res = self.check_otp(otp, window=window, options=options)

        if inc_counter and res >= 0:
            # As usually the counter is increased in lib.token.checkUserPass,
            # we need to do this manually here:
            self.inc_otp_counter(res)
        return res
Exemplo n.º 13
0
    def get_otp(self, current_time=None):
        """
        return the next otp value

        :param curTime: Not Used in HOTP
        :return: next otp value and PIN if possible
        :rtype: tuple
        """
        otplen = int(self.token.otplen)
        secretHOtp = self.token.get_otpkey()

        hmac2Otp = HmacOtp(secretHOtp,
                           self.token.count,
                           otplen,
                           self.get_hashlib(self.hashlib))
        otpval = hmac2Otp.generate(inc_counter=False)

        pin = self.token.get_pin()

        if get_from_config("PrependPin") == "True":
            combined = u"{0!s}{1!s}".format(pin, otpval)
        else:
            combined = u"{0!s}{1!s}".format(otpval, pin)

        return 1, pin, otpval, combined
Exemplo n.º 14
0
def before_request():
    """
    This is executed before the request.

    user_required checks if there is a logged in admin or user

    The checks for ONLY admin are preformed in api/system.py
    """
    # remove session from param and gather all parameters, either
    # from the Form data or from JSON in the request body.
    request.all_data = get_all_params(request.values, request.data)

    g.policy_object = PolicyClass()
    g.audit_object = getAudit(current_app.config)
    g.event_config = EventConfiguration()
    # access_route contains the ip adresses of all clients, hops and proxies.
    g.client_ip = get_client_ip(request,
                                get_from_config(SYSCONF.OVERRIDECLIENT))
    privacyidea_server = current_app.config.get("PI_AUDIT_SERVERNAME") or \
                         request.host
    # Already get some typical parameters to log
    serial = getParam(request.all_data, "serial")
    realm = getParam(request.all_data, "realm")
    user_loginname = ""
    if "token_blueprint" in request.endpoint:
        # In case of token endpoint we evaluate the user in the request.
        # Note: In policy-endpoint "user" is part of the policy configuration
        #  and will cause an exception
        user = get_user_from_param(request.all_data)
        user_loginname = user.login
        realm = user.realm or realm

    g.audit_object.log({"success": False,
                        "serial": serial,
                        "user": user_loginname,
                        "realm": realm,
                        "client": g.client_ip,
                        "client_user_agent": request.user_agent.browser,
                        "privacyidea_server": privacyidea_server,
                        "action": "{0!s} {1!s}".format(request.method, request.url_rule),
                        "action_detail": "",
                        "info": ""})

    if g.logged_in_user.get("role") == "user":
        # A user is calling this API
        # In case the token API is called by the user and not by the admin we
        #  need to restrict the token view.
        CurrentUser = get_user_from_param({"user":
                                               g.logged_in_user.get(
                                                   "username"),
                                           "realm": g.logged_in_user.get(
                                               "realm")})
        request.all_data["user"] = CurrentUser.login
        request.all_data["resolver"] = CurrentUser.resolver
        request.all_data["realm"] = CurrentUser.realm
        g.audit_object.log({"user": CurrentUser.login,
                            "realm": CurrentUser.realm})
    else:
        # An administrator is calling this API
        g.audit_object.log({"administrator": g.logged_in_user.get("username")})
Exemplo n.º 15
0
def before_request():
    """
    This is executed before the request
    """
    update_config_object()
    request.all_data = get_all_params(request.values, request.data)
    request.User = get_user_from_param(request.all_data)
    privacyidea_server = current_app.config.get("PI_AUDIT_SERVERNAME") or \
                         request.host
    # Create a policy_object, that reads the database audit settings
    # and contains the complete policy definition during the request.
    # This audit_object can be used in the postpolicy and prepolicy and it
    # can be passed to the innerpolicies.

    g.policy_object = PolicyClass()

    g.audit_object = getAudit(current_app.config)
    g.event_config = EventConfiguration()
    # access_route contains the ip addresses of all clients, hops and proxies.
    g.client_ip = get_client_ip(request, get_from_config(SYSCONF.OVERRIDECLIENT))
    g.audit_object.log({"success": False,
                        "action_detail": "",
                        "client": g.client_ip,
                        "client_user_agent": request.user_agent.browser,
                        "privacyidea_server": privacyidea_server,
                        "action": "{0!s} {1!s}".format(request.method, request.url_rule),
                        "info": ""})
Exemplo n.º 16
0
    def api_endpoint(cls, request, g):
        """
        This provides a function to be plugged into the API endpoint
        /ttype/u2f

        The u2f token can return the facet list at this URL.

        :param request: The Flask request
        :param g: The Flask global object g
        :return: Flask Response or text
        """
        configured_app_id = get_from_config("u2f.appId")
        if configured_app_id is None:
            raise ParameterError("u2f is not configured")
        app_id = configured_app_id.strip("/")

        # Read the facets from the policies
        pol_facets = g.policy_object.get_action_values(U2FACTION.FACETS,
                                                       scope=SCOPE.AUTH,
                                                       client=g.client_ip,
                                                       audit_data=g.audit_object.audit_data)
        facet_list = ["https://{0!s}".format(x) for x in pol_facets]
        facet_list.append(app_id)

        log.debug("Sending facets lists for appId {0!s}: {1!s}".format(app_id,
                                                             facet_list))
        res = {"trustedFacets": [{"version": {"major": 1,
                                              "minor": 0},
                                  "ids": facet_list
                                  }
                                 ]
               }
        return "fido.trusted-apps+json", res
Exemplo n.º 17
0
    def get_otp(self, current_time=None, do_truncation=True, time_seconds=None, challenge=None):
        """
        get the next OTP value

        :param current_time: the current time, for which the OTP value
        should be calculated for.
        :type current_time: datetime object
        :param time_seconds: the current time, for which the OTP value
        should be calculated for (date +%s)
        :type: time_seconds: int, unix system time seconds
        :return: next otp value, and PIN, if possible
        :rtype: tuple
        """
        otplen = int(self.token.otplen)
        secretHOtp = self.token.get_otpkey()

        hmac2Otp = HmacOtp(secretHOtp, self.get_otp_count(), otplen, self.get_hashlib(self.hashlib))

        if time_seconds is None:
            time_seconds = self._time2float(datetime.datetime.now())
        if current_time:
            time_seconds = self._time2float(current_time)

        # we don't need to round here as we have already float
        counter = int(((time_seconds - self.timeshift) / self.timestep))
        otpval = hmac2Otp.generate(counter=counter, inc_counter=False, do_truncation=do_truncation, challenge=challenge)

        pin = self.token.get_pin()
        combined = "%s%s" % (otpval, pin)
        if get_from_config("PrependPin") == "True":
            combined = "%s%s" % (pin, otpval)

        return 1, pin, otpval, combined
Exemplo n.º 18
0
    def _autosync(self, hmac2Otp, anOtpVal):
        """
        synchronize the token based on two otp values automatically.
        If the OTP is invalid, that OTP counter is stored.
        If an old OTP counter is stored, it is checked, if the new
        OTP value is the next value after this counter.

        internal method to realize the _autosync within the
        checkOtp method

        :param hmac2Otp: the hmac object (with reference to the token secret)
        :type hmac2Otp: hmac object
        :param anOtpVal: the actual otp value
        :type anOtpVal: string
        :return: counter or -1 if otp does not exist
        :rtype:  int
        """
        res = -1
        autosync = get_from_config("AutoResync", False, return_bool=True)
        # if _autosync is not enabled: do nothing
        if autosync is False:
            return res

        info = self.get_tokeninfo()
        syncWindow = self.get_sync_window()

        # check if the otpval is valid in the sync scope
        res = hmac2Otp.checkOtp(anOtpVal, syncWindow, symetric=True)
        log.debug("found otpval {0!r} in syncwindow ({1!r}): {2!r}".format(anOtpVal, syncWindow, res))

        if res != -1:
            # if former is defined
            if "otp1c" in info:
                # check if this is consecutive
                otp1c = int(info.get("otp1c"))
                otp2c = res
                log.debug("otp1c: {0!r}, otp2c: {1!r}".format(otp1c, otp2c))
                diff = math.fabs(otp2c - otp1c)
                if diff > self.resyncDiffLimit:
                    res = -1
                else:
                    server_time = time.time()
                    counter = int((server_time / self.timestep) + 0.5)

                    shift = otp2c - counter
                    info["timeShift"] = shift
                    self.set_tokeninfo(info)

                # now clean the resync data
                del info["otp1c"]
                self.set_tokeninfo(info)

            else:
                log.debug("setting otp1c: {0!s}".format(res))
                info["otp1c"] = res
                self.set_tokeninfo(info)
                res = -1

        return res
Exemplo n.º 19
0
    def test_06_public_and_admin(self):
        # This tests the new public available config
        set_privacyidea_config("publicInfo1", "info1", typ="public")
        set_privacyidea_config("publicInfo2", "info2", typ="public")
        set_privacyidea_config("secretInfo1", "info1")

        # Get administrators info
        a = get_from_config()
        self.assertTrue("secretInfo1" in a)
        self.assertTrue("publicInfo1" in a)
        a = get_from_config("publicInfo1")
        self.assertEqual(a, "info1")
        a = get_from_config("secretInfo1")
        self.assertEqual(a, "info1")

        # Get public info as user
        a = get_from_config()
        self.assertTrue("publicInfo1" in a)
        a = get_from_config("publicInfo1")
        self.assertEqual(a, "info1")

        # Not able to get private info as user
        a = get_from_config(role="public")
        self.assertTrue("secretInfo1" not in a)
        a = get_from_config("secretInfo1", role="public")
        self.assertEqual(a, None)
Exemplo n.º 20
0
    def _get_api_key(api_id):
        """
        Return the symmetric key for the given apiId.

        :param apiId: The base64 encoded API ID
        :return: the base64 encoded API Key or None
        """
        api_key = get_from_config("yubikey.apiid.%s" % api_id)
        return api_key
Exemplo n.º 21
0
    def _get_sms_provider_config():
        """
        load the defined sms provider config definition

        :return: dict of the sms provider definition
        :rtype: dict
        """
        tConfig = get_from_config("sms.providerConfig", "{}")
        config = loads(tConfig)
        return config
Exemplo n.º 22
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
        """
        ret = None

        phone = self.get_tokeninfo("phone")
        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
        sms_gateway_identifier = 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
Exemplo n.º 23
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
        :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 = "Enter the OTP from the Email:"
        attributes = {'state': transactionid}

        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_email_text_or_subject(options)
                subject_template = self._get_email_text_or_subject(options,
                                                                   EMAILACTION.EMAILSUBJECT,
                                                                   "Your OTP")
                validity = int(get_from_config("email.validtime", 120))

                # 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
                # We send the email after creating the challenge for testing.
                success, sent_message = self._compose_email(
                    message=message_template,
                    subject=subject_template)

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

        return success, return_message, transactionid, attributes
Exemplo n.º 24
0
def get_cache_time():
    """
    :return: UserCacheExpiration config value as a timedelta
    :rtype: timedelta
    """
    seconds = 0
    try:
        seconds = int(get_from_config(EXPIRATION_SECONDS, '0'))
    except ValueError:
        log.info(u"Non-Integer value stored in system config {0!s}".format(EXPIRATION_SECONDS))

    return datetime.timedelta(seconds=seconds)
Exemplo n.º 25
0
def get_subscription():
    """
    Returns the license from the Config database
    """
    DEFAULT_SUB = """{'systemid': 'unknown',
                      'customername': 'Not registered',
                      'subscription': -1,
                      'supportlevel': 'No Support',
                      'expires': 'never',
                      'signature': None}"""
    subscription = yaml.load(get_from_config("subscription", DEFAULT_SUB))
    return subscription
Exemplo n.º 26
0
    def _get_sms_provider():
        """
        get the SMS Provider class definition

        :return: tuple of SMSProvider and Provider Class as string
        :rtype: tuple of (string, string)
        """
        smsProvider = get_from_config("sms.provider",
                                      default="privacyidea.lib.smsprovider."
                                              "HttpSMSProvider.HttpSMSProvider")
        (SMSProvider, SMSProviderClass) = smsProvider.rsplit(".", 1)
        return SMSProvider, SMSProviderClass
Exemplo n.º 27
0
    def test_00_get_config(self):
        # set the config
        set_privacyidea_config(key="Hallo", value="What?", typ="string",
                               desc="Some dumb value")

        # get the complete config
        conf = get_from_config()
        self.assertTrue("Hallo" in conf, conf)

        conf = get_from_config("Hallo")
        self.assertTrue(conf == "What?", conf)

        conf = get_from_config("Hello", "Does not exist")
        self.assertTrue(conf == "Does not exist", conf)

        conf = get_privacyidea_config()
        self.assertTrue("Hallo" in conf, conf)

        # delete privacyidea config
        delete_privacyidea_config("Hallo")
        conf = get_from_config("Hallo")
        self.assertFalse(conf == "What?", conf)

        # set more values to create a timestamp and overwrite
        set_privacyidea_config(key="k1", value="v1")
        set_privacyidea_config(key="k2", value="v2")
        set_privacyidea_config(key="k3", value="v3")
        conf = get_from_config("k3")
        self.assertTrue(conf == "v3", conf)
        set_privacyidea_config(key="k3", value="new", typ="string", desc="n")
        conf = get_from_config("k3")
        self.assertTrue(conf == "new", conf)
Exemplo n.º 28
0
    def _get_sms_timeout():
        """
        get the challenge time is in the specified range

        :return: the defined validation timeout in seconds
        :rtype:  int
        """
        try:
            timeout = int(get_from_config("sms.providerTimeout", 5 * 60))
        except Exception as ex:  # pragma: no cover
            log.warning("SMSProviderTimeout: value error {0!r} - reset to 5*60".format((ex)))
            timeout = 5 * 60
        return timeout
Exemplo n.º 29
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

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

        # check if the key_size id provided
        # if not, we could derive it from the hashlib
        key_size = getParam(upd_param, 'key_size', optional)
        if key_size is None:
            upd_param['key_size'] = keylen.get(hashlibStr)
        otpKey = ''
        if self.hKeyRequired is True:
            genkey = int(getParam(upd_param, "genkey", optional) or 0)
            if 1 == genkey:
                # if hashlibStr not in keylen dict, this will
                # raise an Exception
                otpKey = generate_otpkey(upd_param['key_size'])
                del upd_param['genkey']
            else:
                # genkey not set: check otpkey is given
                # this will raise an exception if otpkey is not present
                otpKey = getParam(upd_param, "otpkey", required)
                # finally set the values for the update
            upd_param['otpkey'] = otpKey
        upd_param['hashlib'] = hashlibStr
        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))

        TokenClass.update(self, upd_param, reset_failcount)
Exemplo n.º 30
0
    def _autosync(self, hmac2Otp, anOtpVal):
        """
        synchronize the token based on two otp values automatically.
        If the OTP is invalid, that OTP counter is stored.
        If an old OTP counter is stored, it is checked, if the new
        OTP value is the next value after this counter.

        internal method to realize the _autosync within the
        checkOtp method

        :param hmac2Otp: the hmac object (with reference to the token secret)
        :type hmac2Otp: hmac object
        :param anOtpVal: the actual otp value
        :type anOtpVal: string
        :return: counter or -1 if otp does not exist
        :rtype:  int
        """
        res = -1
        autosync = False

        async = get_from_config("AutoResync")
        if async is None:
            autosync = False
Exemplo n.º 31
0
    def update(self, param):
        """
        This method is called during the initialization process.

        :param param: parameters from the token init
        :type param: dict
        :return: None
        """
        # We should only initialize such a token, when the user is
        # immediately given in the init process, since the token on the
        # smartphone needs to contain a userId.
        if not self.user:
            # The user and realms should have already been set in init_token()
            raise ParameterError("Missing parameter: {0!r}".format("user"),
                                 id=905)

        ocrasuite = get_from_config("tiqr.ocrasuite") or OCRA_DEFAULT_SUITE
        OCRASuite(ocrasuite)
        self.add_tokeninfo("ocrasuite", ocrasuite)
        TokenClass.update(self, param)
Exemplo n.º 32
0
def before_request():
    """
    This is executed before the request
    """
    update_config_object()
    request.all_data = get_all_params(request.values, request.data)
    privacyidea_server = current_app.config.get("PI_AUDIT_SERVERNAME") or \
                         request.host
    g.policy_object = PolicyClass()
    g.audit_object = getAudit(current_app.config)
    # access_route contains the ip adresses of all clients, hops and proxies.
    g.client_ip = get_client_ip(request,
                                get_from_config(SYSCONF.OVERRIDECLIENT))
    g.audit_object.log({"success": False,
                        "client": g.client_ip,
                        "client_user_agent": request.user_agent.browser,
                        "privacyidea_server": privacyidea_server,
                        "action": "{0!s} {1!s}".format(request.method, request.url_rule),
                        "action_detail": "",
                        "info": ""})
Exemplo n.º 33
0
    def get_otp(self,
                current_time=None,
                do_truncation=True,
                time_seconds=None,
                challenge=None):
        """
        get the next OTP value

        :param current_time: the current time, for which the OTP value
        should be calculated for.
        :type current_time: datetime object
        :param time_seconds: the current time, for which the OTP value
        should be calculated for (date +%s)
        :type: time_seconds: int, unix system time seconds
        :return: next otp value, and PIN, if possible
        :rtype: tuple
        """
        otplen = int(self.token.otplen)
        secretHOtp = self.token.get_otpkey()

        hmac2Otp = HmacOtp(secretHOtp, self.get_otp_count(), otplen,
                           self.get_hashlib(self.hashlib))

        if time_seconds is None:
            time_seconds = self._time2float(datetime.datetime.now())
        if current_time:
            time_seconds = self._time2float(current_time)

        # we don't need to round here as we have already float
        counter = int(((time_seconds - self.timeshift) / self.timestep))
        otpval = hmac2Otp.generate(counter=counter,
                                   inc_counter=False,
                                   do_truncation=do_truncation,
                                   challenge=challenge)

        pin = self.token.get_pin()
        combined = "{0!s}{1!s}".format(otpval, pin)
        if get_from_config("PrependPin") == "True":
            combined = "{0!s}{1!s}".format(pin, otpval)

        return 1, pin, otpval, combined
Exemplo n.º 34
0
    def _compose_email(self, message="<otp>", subject="Your OTP"):
        """
        send email

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

        :return: submitted message
        :rtype: string
        """
        ret = None

        recipient = self._email_address
        otp = self.get_otp()[2]
        serial = self.get_serial()

        message = message.replace("<otp>", otp)
        message = message.replace("<serial>", serial)

        subject = subject.replace("<otp>", otp)
        subject = subject.replace("<serial>", serial)

        log.debug("sending Email to %s " % recipient)

        identifier = get_from_config("email.identifier")
        if identifier:
            # New way to send email
            ret = send_email_identifier(identifier, recipient, subject,
                                        message)
        else:
            # old way to send email / DEPRECATED
            mailserver = get_from_config("email.mailserver", "localhost")
            port = int(get_from_config("email.port", 25))
            username = get_from_config("email.username")
            password = get_from_config("email.password")
            mail_from = get_from_config("email.mailfrom",
                                        "privacyidea@localhost")
            email_tls = get_from_config("email.tls")
            ret = send_email_data(mailserver, subject, message, mail_from,
                                  recipient, username, password, port,
                                  email_tls)
        return ret, message
Exemplo n.º 35
0
    def update(self, param):
        """
        This method is called during the initialization process.

        :param param: parameters from the token init
        :type param: dict
        :return: None
        """
        # We should only initialize such a token, when the user is
        # immediately given in the init process, since the token on the
        # smartphone needs to contain a userId.
        user_object = get_user_from_param(param, required)
        self.set_user(user_object)

        ocrasuite = get_from_config("tiqr.ocrasuite") or OCRA_DEFAULT_SUITE
        OCRASuite(ocrasuite)
        self.add_tokeninfo("ocrasuite", ocrasuite)
        TokenClass.update(self, param)
        # We have to set the realms here, since the token DB object does not
        # have an ID before TokenClass.update.
        self.set_realms([user_object.realm])
Exemplo n.º 36
0
def before_request():
    """
    This is executed before the request
    """
    ensure_no_config_object()
    request.all_data = get_all_params(request)
    request.User = get_user_from_param(request.all_data)
    privacyidea_server = current_app.config.get("PI_AUDIT_SERVERNAME") or \
                         request.host
    # Create a policy_object, that reads the database audit settings
    # and contains the complete policy definition during the request.
    # This audit_object can be used in the postpolicy and prepolicy and it
    # can be passed to the innerpolicies.

    g.policy_object = PolicyClass()

    g.audit_object = getAudit(current_app.config, g.startdate)
    g.event_config = EventConfiguration()
    # access_route contains the ip addresses of all clients, hops and proxies.
    g.client_ip = get_client_ip(request,
                                get_from_config(SYSCONF.OVERRIDECLIENT))
    # Save the HTTP header in the localproxy object
    g.request_headers = request.headers
    g.serial = getParam(request.all_data, "serial", default=None)
    g.audit_object.log({
        "success":
        False,
        "action_detail":
        "",
        "client":
        g.client_ip,
        "client_user_agent":
        request.user_agent.browser,
        "privacyidea_server":
        privacyidea_server,
        "action":
        "{0!s} {1!s}".format(request.method, request.url_rule),
        "info":
        ""
    })
Exemplo n.º 37
0
    def get_otp(self, current_time=None):
        """
        return the next otp value

        :param curTime: Not Used in HOTP
        :return: next otp value and PIN if possible
        :rtype: tuple
        """
        otplen = int(self.token.otplen)
        secretHOtp = self.token.get_otpkey()

        hmac2Otp = HmacOtp(secretHOtp, self.token.count, otplen,
                           self.get_hashlib(self.hashlib))
        otpval = hmac2Otp.generate(inc_counter=False)

        pin = self.token.get_pin()
        combined = "{0!s}{1!s}".format(otpval, pin)

        if get_from_config("PrependPin") == "True":
            combined = "{0!s}{1!s}".format(pin, otpval)

        return 1, pin, otpval, combined
Exemplo n.º 38
0
    def get_init_detail(self, params=None, user=None):
        """
        At the end of the initialization we return the URL for the TiQR App.
        """
        response_detail = TokenClass.get_init_detail(self, params, user)
        params = params or {}
        enroll_url = get_from_config("tiqr.regServer")
        log.info("using tiqr.regServer for enrollment: %s" % enroll_url)
        serial = self.token.serial
        session = generate_otpkey()
        # save the session in the token
        self.add_tokeninfo("session", session)
        tiqrenroll = "tiqrenroll://%s?action=%s&session=%s&serial=%s" % (
            enroll_url, API_ACTIONS.METADATA, session, serial)

        response_detail["tiqrenroll"] = {
            "description": _("URL for TiQR "
                             "enrollment"),
            "value": tiqrenroll,
            "img": create_img(tiqrenroll, width=250)
        }

        return response_detail
Exemplo n.º 39
0
    def api_endpoint(cls, request, g):
        """
        This provides a function to be plugged into the API endpoint
        /ttype/u2f

        The u2f token can return the facet list at this URL.

        :param request: The Flask request
        :param g: The Flask global object g
        :return: Flask Response or text
        """
        configured_app_id = get_from_config("u2f.appId")
        if configured_app_id is None:
            raise ParameterError("u2f is not configured")
        app_id = configured_app_id.strip("/")

        # Read the facets from the policies
        pol_facets = g.policy_object.get_action_values(
            U2FACTION.FACETS,
            scope=SCOPE.AUTH,
            client=g.client_ip,
            audit_data=g.audit_object.audit_data)
        facet_list = ["https://{0!s}".format(x) for x in pol_facets]
        facet_list.append(app_id)

        log.debug("Sending facets lists for appId {0!s}: {1!s}".format(
            app_id, facet_list))
        res = {
            "trustedFacets": [{
                "version": {
                    "major": 1,
                    "minor": 0
                },
                "ids": facet_list
            }]
        }
        return "fido.trusted-apps+json", res
Exemplo n.º 40
0
def before_request():
    """
    This is executed before the request
    """
    ensure_no_config_object()
    request.all_data = get_all_params(request.values, request.data)
    privacyidea_server = current_app.config.get("PI_AUDIT_SERVERNAME") or \
                         request.host
    g.policy_object = PolicyClass()
    g.audit_object = getAudit(current_app.config)
    g.event_config = EventConfiguration()
    # access_route contains the ip adresses of all clients, hops and proxies.
    g.client_ip = get_client_ip(request,
                                get_from_config(SYSCONF.OVERRIDECLIENT))
    g.serial = getParam(request.all_data, "serial", default=None)
    g.audit_object.log({"success": False,
                        "client": g.client_ip,
                        "client_user_agent": request.user_agent.browser,
                        "privacyidea_server": privacyidea_server,
                        "action": "{0!s} {1!s}".format(request.method, request.url_rule),
                        "action_detail": "",
                        "info": ""})

    username = getParam(request.all_data, "username")
    if username:
        # We only fill request.User, if we really have a username.
        # On endpoints like /auth/rights, this is not available
        loginname, realm = split_user(username)
        # overwrite the split realm if we have a realm parameter. Default back to default_realm
        realm = getParam(request.all_data, "realm", default=realm) or realm or get_default_realm()
        # Prefill the request.User. This is used by some pre-event handlers
        try:
            request.User = User(loginname, realm)
        except Exception as e:
            request.User = None
            log.warning(u"Problem resolving user {0!s} in realm {1!s}: {2!s}.".format(loginname, realm, e))
            log.debug(u"{0!s}".format(traceback.format_exc()))
Exemplo n.º 41
0
def before_request():
    """
    This is executed before the request
    """
    g.config_object = ConfigClass()
    request.all_data = get_all_params(request.values, request.data)
    privacyidea_server = current_app.config.get("PI_AUDIT_SERVERNAME") or \
                         request.host
    # Create a policy_object, that reads the database audit settings
    # and contains the complete policy definition during the request.
    # This audit_object can be used in the postpolicy and prepolicy and it
    # can be passed to the innerpolicies.
    g.policy_object = PolicyClass()
    g.audit_object = getAudit(current_app.config)
    # access_route contains the ip adresses of all clients, hops and proxies.
    g.client_ip = get_client_ip(request,
                                get_from_config(SYSCONF.OVERRIDECLIENT))
    g.audit_object.log({"success": False,
                        "action_detail": "",
                        "client": g.client_ip,
                        "client_user_agent": request.user_agent.browser,
                        "privacyidea_server": privacyidea_server,
                        "action": "{0!s} {1!s}".format(request.method, request.url_rule),
                        "info": ""})
Exemplo n.º 42
0
    def update(self, param):
        """
        This method is called during the initialization process.

        :param param: parameters from the token init
        :type param: dict
        :return: None
        """
        j_questions = getParam(param, "questions", required)
        try:
            # If we have a string, we load the json format
            questions = json.loads(j_questions)
        except TypeError:
            # Obviously we have a dict...
            questions = j_questions
        num_answers = get_from_config("question.num_answers",
                                      DEFAULT_NUM_ANSWERS)
        if len(questions) < int(num_answers):
            raise TokenAdminError(_("You need to provide at least %s "
                                    "answers.") % num_answers)
        # Save all questions and answers and encrypt them
        for question, answer in questions.items():
            self.add_tokeninfo(question, answer, value_type="password")
        TokenClass.update(self, param)
Exemplo n.º 43
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
Exemplo n.º 44
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]:
            if ssl_verify.lower() in ["true", "1"]:
                ssl_verify = True
            else:
                ssl_verify = False

        # 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
Exemplo n.º 45
0
    def _compose_email(self,
                       message="<otp>",
                       subject="Your OTP",
                       mimetype="plain"):
        """
        send email

        :param message: the email submit message - could contain placeholders
            like <otp> or <serial>
        :type message: string
        :param mimetype: the message MIME type - one of "plain", "html"
        :type mimetype: basestring

        :return: submitted message
        :rtype: string
        """
        ret = None

        recipient = self._email_address
        otp = self.get_otp()[2]
        serial = self.get_serial()

        message = message.replace("<otp>", otp)
        message = message.replace("<serial>", serial)

        tags = create_tag_dict(
            serial=serial,
            tokenowner=self.user,
            tokentype=self.get_tokentype(),
            recipient={
                "givenname":
                self.user.info.get("givenname") if self.user else "",
                "surname": self.user.info.get("surname") if self.user else ""
            },
            escape_html=mimetype.lower() == "html")

        message = message.format(otp=otp, **tags)

        subject = subject.replace("<otp>", otp)
        subject = subject.replace("<serial>", serial)

        subject = subject.format(otp=otp, **tags)

        log.debug("sending Email to {0!r}".format(recipient))

        # The token specific identifier has priority over the system wide identifier
        identifier = self.get_tokeninfo("email.identifier") or get_from_config(
            "email.identifier")
        if identifier:
            # New way to send email
            ret = send_email_identifier(identifier,
                                        recipient,
                                        subject,
                                        message,
                                        mimetype=mimetype)
        else:
            # old way to send email / DEPRECATED
            mailserver = get_from_config("email.mailserver", "localhost")
            port = int(get_from_config("email.port", 25))
            username = get_from_config("email.username")
            password = get_from_config("email.password")
            mail_from = get_from_config("email.mailfrom",
                                        "privacyidea@localhost")
            email_tls = get_from_config("email.tls",
                                        default=False,
                                        return_bool=True)
            ret = send_email_data(mailserver, subject, message, mail_from,
                                  recipient, username, password, port,
                                  email_tls)
        return ret, message
Exemplo n.º 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 (success, message, transactionid, attributes)

                * success: if submit was successful
                * message: the text submitted to the user
                * transactionid: the given or generated transactionid
                * reply_dict: additional dictionary, which is added to the response
        :rtype: tuple(bool, str, str, dict)
        """
        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:")
        reply_dict = {'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)
        reply_dict['attributes']['valid_until'] = "{0!s}".format(expiry_date)

        return success, return_message, transactionid, reply_dict
Exemplo n.º 47
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)
            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."
            )

        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()
        return True, message, db_challenge.transaction_id, attributes
Exemplo n.º 48
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, "{0!s}_{1!s}".format(
                self.get_class_type(), ACTION.CHALLENGETEXT), options) or _(
                    u'Please confirm with your U2F token ({0!s})').format(
                        self.token.description)

        validity = int(get_from_config('DefaultChallengeValidityTime', 120))
        tokentype = self.get_tokentype().lower()
        lookup_for = tokentype.capitalize() + 'ChallengeValidityTime'
        validity = int(get_from_config(lookup_for, validity))

        challenge = geturandom(32)
        # Create the challenge in the database
        db_challenge = Challenge(self.token.serial,
                                 transaction_id=transactionid,
                                 challenge=hexlify_and_unicode(challenge),
                                 data=None,
                                 session=options.get("session"),
                                 validitytime=validity)
        db_challenge.save()
        sec_object = self.token.get_otpkey()
        key_handle_hex = sec_object.getKey()
        key_handle_bin = binascii.unhexlify(key_handle_hex)
        key_handle_url = url_encode(key_handle_bin)
        challenge_url = url_encode(challenge)
        u2f_sign_request = {
            "appId": self.get_tokeninfo("appId"),
            "version": U2F_Version,
            "challenge": challenge_url,
            "keyHandle": key_handle_url
        }

        image_url = IMAGES.get(self.token.description.lower().split()[0], "")
        response_details = {
            "u2fSignRequest": u2f_sign_request,
            "hideResponseInput": True,
            "img": image_url
        }

        return True, message, db_challenge.transaction_id, response_details
Exemplo n.º 49
0
    def _autosync(self, hmac2Otp, anOtpVal):
        """
        automatically sync the token based on two otp values
        internal method to implement the _autosync within the
        checkOtp method.

        :param hmac2Otp: the hmac object (with reference to the token secret)
        :type hmac2Otp: hmac object

        :param anOtpVal: the actual otp value
        :type anOtpVal: string

        :return: counter or -1 if otp does not exist
        :rtype:  int
        """
        res = -1
        # get _autosync from config or use False as default
        autosync = get_from_config("AutoResync", False, return_bool=True)

        # if _autosync is not enabled
        if autosync is False:
            log.debug("end. _autosync is not enabled : res {0!r}".format((res)))
            return res

        info = self.get_tokeninfo()
        syncWindow = self.get_sync_window()

        # check if the otpval is valid in the sync scope
        res = hmac2Otp.checkOtp(anOtpVal, syncWindow)

        # If the otpval is valid in the big sync scope, we
        # either store the value in the tokeninfo
        # or see if already another value exists.
        if res != -1:
            # if former is defined
            if "otp1c" in info:
                # check if this is consecutive
                otp1c = int(info.get("otp1c"))
                otp2c = res

                if (otp1c + 1) != otp2c:
                    res = -1

                if "dueDate" in info:
                    dueDate = int(info.get("dueDate"))
                    now = int(time.time())
                    if dueDate <= now:
                        res = -1
                else:
                    # if by any reason the dueDate is missing!
                    res = -1  # pragma: no cover

                # now clean the resync data
                self.del_tokeninfo("dueDate")
                self.del_tokeninfo("otp1c")

            else:
                self.add_tokeninfo("otp1c", res)
                self.add_tokeninfo("dueDate", int(time.time()) +
                                   self.get_sync_timeout())

                res = -1

        return res
Exemplo n.º 50
0
 def hashlib(self):
     hashlibStr = self.get_tokeninfo("hashlib") or \
                  get_from_config("hotp.hashlib", u'sha1')
     return hashlibStr
Exemplo n.º 51
0
 def __init__(self, aToken):
     HotpTokenClass.__init__(self, aToken)
     self.set_type(u"email")
     # we support various hashlib methods, but only on create
     # which is effectively set in the update
     self.hashlibStr = get_from_config("hotp.hashlib", "sha1")
Exemplo n.º 52
0
 def timewindow(self):
     window = int(
         self.get_tokeninfo("timeWindow")
         or get_from_config("totp.timeWindow") or 180)
     return window
Exemplo n.º 53
0
 def timestep(self):
     timeStepping = int(
         self.get_tokeninfo("timeStep") or get_from_config("totp.timeStep")
         or 30)
     return timeStepping
Exemplo n.º 54
0
def get_auth_token():
    """
    This call verifies the credentials of the user and issues an
    authentication token, that is used for the later API calls. The
    authentication token has a validity, that is usually 1 hour.

    :jsonparam username: The username of the user who wants to authenticate to
        the API.
    :jsonparam password: The password/credentials of the user who wants to
        authenticate to the API.
    :jsonparam realm: The realm where the user will be searched.

    :return: A json response with an authentication token, that needs to be
        used in any further request.

    :status 200: in case of success
    :status 401: if authentication fails

    **Example Authentication Request**:

    .. sourcecode:: http

       POST /auth HTTP/1.1
       Host: example.com
       Accept: application/json

       username=admin
       password=topsecret

    **Example Authentication Response**:

    .. sourcecode:: http

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

       {
            "id": 1,
            "jsonrpc": "2.0",
            "result": {
                "status": true,
                "value": {
                    "token": "eyJhbGciOiJIUz....jdpn9kIjuGRnGejmbFbM"
                }
            },
            "version": "privacyIDEA unknown"
       }

    **Response for failed authentication**:

    .. sourcecode:: http

       HTTP/1.1 401 UNAUTHORIZED
       Content-Type: application/json
       Content-Length: 203

       {
          "id": 1,
          "jsonrpc": "2.0",
          "result": {
            "error": {
              "code": -401,
              "message": "missing Authorization header"
            },
            "status": false
          },
          "version": "privacyIDEA unknown",
          "config": {
            "logout_time": 30
          }
       }

    """
    validity = timedelta(hours=1)
    username = getParam(request.all_data, "username")
    password = getParam(request.all_data, "password")
    realm_param = getParam(request.all_data, "realm")
    details = {}
    realm = ''

    # the realm parameter has precedence! Check if it exists
    if realm_param and not realm_is_defined(realm_param):
        raise AuthError(_(
            "Authentication failure. Unknown realm: {0!s}.".format(
                realm_param)),
                        id=ERROR.AUTHENTICATE_WRONG_CREDENTIALS)

    if username is None:
        raise AuthError(_("Authentication failure. Missing Username"),
                        id=ERROR.AUTHENTICATE_MISSING_USERNAME)

    loginname = username
    split_at_sign = get_from_config(SYSCONF.SPLITATSIGN, return_bool=True)
    if split_at_sign:
        (loginname, realm) = split_user(username)

    # overwrite the splitted realm if we have a realm parameter
    if realm_param:
        realm = realm_param

    # and finally check if there is a realm
    realm = realm or get_default_realm()

    # Failsafe to have the user attempt in the log, whatever happens
    # This can be overwritten later
    g.audit_object.log({"user": username, "realm": realm})

    secret = current_app.secret_key
    superuser_realms = current_app.config.get("SUPERUSER_REALM", [])
    # This is the default role for the logged in user.
    # The role privileges may be risen to "admin"
    role = ROLE.USER
    # The way the user authenticated. This could be
    # "password" = The admin user DB or the user store
    # "pi" = The admin or the user is authenticated against privacyIDEA
    # "remote_user" = authenticated by webserver
    authtype = "password"
    # Verify the password
    admin_auth = False
    user_auth = False

    user_obj = User()

    # Check if the remote user is allowed
    if (request.remote_user == username) and is_remote_user_allowed(request):
        # Authenticated by the Web Server
        # Check if the username exists
        # 1. in local admins
        # 2. in a realm
        # 2a. is an admin realm
        authtype = "remote_user "
        if db_admin_exist(username):
            role = ROLE.ADMIN
            admin_auth = True
            g.audit_object.log({
                "success": True,
                "user": "",
                "administrator": username,
                "info": "internal admin"
            })
        else:
            # check, if the user exists
            user_obj = User(loginname, realm)
            g.audit_object.log({
                "user": user_obj.login,
                "realm": user_obj.realm,
                "info": log_used_user(user_obj)
            })
            if user_obj.exist():
                user_auth = True
                if user_obj.realm in superuser_realms:
                    role = ROLE.ADMIN
                    admin_auth = True

    elif verify_db_admin(username, password):
        role = ROLE.ADMIN
        admin_auth = True
        # This admin is not in the default realm!
        realm = ""
        g.audit_object.log({
            "success": True,
            "user": "",
            "administrator": username,
            "info": "internal admin"
        })

    else:
        # The user could not be identified against the admin database,
        # so we do the rest of the check
        options = {"g": g, "clientip": g.client_ip}
        for key, value in request.all_data.items():
            if value and key not in ["g", "clientip"]:
                options[key] = value
        user_obj = User(loginname, realm)
        user_auth, role, details = check_webui_user(
            user_obj,
            password,
            options=options,
            superuser_realms=superuser_realms)
        details = details or {}
        if role == ROLE.ADMIN:
            g.audit_object.log({
                "user":
                "",
                "administrator":
                user_obj.login,
                "realm":
                user_obj.realm,
                "resolver":
                user_obj.resolver,
                "serial":
                details.get('serial', None),
                "info":
                u"{0!s}|loginmode={1!s}".format(log_used_user(user_obj),
                                                details.get("loginmode"))
            })
        else:
            g.audit_object.log({
                "user":
                user_obj.login,
                "realm":
                user_obj.realm,
                "resolver":
                user_obj.resolver,
                "serial":
                details.get('serial', None),
                "info":
                u"{0!s}|loginmode={1!s}".format(log_used_user(user_obj),
                                                details.get("loginmode"))
            })

    if not admin_auth and not user_auth:
        raise AuthError(_("Authentication failure. Wrong credentials"),
                        id=ERROR.AUTHENTICATE_WRONG_CREDENTIALS,
                        details=details or {})
    else:
        g.audit_object.log({"success": True})
        request.User = user_obj

    # If the HSM is not ready, we need to create the nonce in another way!
    hsm = init_hsm()
    if hsm.is_ready:
        nonce = geturandom(hex=True)
        # Add the role to the JWT, so that we can verify it internally
        # Add the authtype to the JWT, so that we could use it for access
        # definitions
        rights = g.policy_object.ui_get_rights(role, realm, loginname,
                                               g.client_ip)
        menus = g.policy_object.ui_get_main_menus(
            {
                "username": loginname,
                "role": role,
                "realm": realm
            }, g.client_ip)
    else:
        import os
        nonce = hexlify_and_unicode(os.urandom(20))
        rights = []
        menus = []

    # What is the log level?
    log_level = current_app.config.get("PI_LOGLEVEL", 30)

    token = jwt.encode(
        {
            "username": loginname,
            "realm": realm,
            "nonce": nonce,
            "role": role,
            "authtype": authtype,
            "exp": datetime.utcnow() + validity,
            "rights": rights
        },
        secret,
        algorithm='HS256').decode('utf8')

    # Add the role to the response, so that the WebUI can make decisions
    # based on this (only show selfservice, not the admin part)
    return send_result(
        {
            "token": token,
            "role": role,
            "username": loginname,
            "realm": realm,
            "log_level": log_level,
            "rights": rights,
            "menus": menus
        },
        details=details)
Exemplo n.º 55
0
class ScriptEventHandler(BaseEventHandler):
    """
    An Eventhandler needs to return a list of actions, which it can handle.
    It also returns a list of allowed action and conditions
    It returns an identifier, which can be used in the eventhandlig definitions
    """

    identifier = "Script"
    description = "This event handler can trigger external scripts."

    try:
        script_directory = get_from_config("PI_SCRIPT_HANDLER_DIRECTORY",
                                           "/etc/privacyidea/scripts")
    except RuntimeError:
        # In case of the tests we are outside of the application context
        script_directory = "tests/testdata/scripts"

    @property
    def actions(cls):
        """
        This method returns a dictionary of allowed actions and possible
        options in this handler module.

        :return: dict with actions
        """
        scripts = os.listdir(cls.script_directory)
        actions = {}
        for script in scripts:
            actions[script] = {
                "serial": {
                    "type": "bool",
                    "description": _("Add '--serial <serial number>' as script "
                                     "parameter.")
                },
                "user": {
                    "type": "bool",
                    "description": _("Add '--user <username>' as script "
                                     "parameter.")
                },
                "realm": {
                    "type": "bool",
                    "description": _("Add '--realm <realm>' as script "
                                     "parameter.")
                },
                "logged_in_user": {
                    "type": "bool",
                    "description": _("Add the username of the logged in user "
                                     "as script parameter like "
                                     "'--logged_in_user <username>'.")
                },
                "logged_in_role": {
                    "type": "bool",
                    "description": _("Add the role (either admin or user) of "
                                     "the logged in user as script parameter "
                                     "like '--logged_in_role <role>'.")
                }
            }

        return actions

    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 = json.loads(response.data)
        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"))

        try:
            p = subprocess.Popen(proc_args, cwd=self.script_directory)
            log.info("Started script {script!r}:"
                     " {process!r}".format(script=script_name, process=p))
        except Exception as e:
            log.warning("Failed to execute script {0!r}: {1!r}".format(
                script_name, e))
            log.warning(traceback.format_exc())

        return ret
Exemplo n.º 56
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 challenge ``reply_dict``, which are displayed in the JSON challenges response.
        """
        options = options or {}
        message = get_action_values_from_options(
            SCOPE.AUTH, ACTION.CHALLENGETEXT,
            options) or DEFAULT_CHALLENGE_TEXT

        reply_dict = {}
        data = None
        # Initially we assume there is no error from Firebase
        res = True
        fb_identifier = self.get_tokeninfo(PUSH_ACTION.FIREBASE_CONFIG)
        if fb_identifier:
            challenge = b32encode_and_unicode(geturandom())
            if fb_identifier != POLL_ONLY:
                # We only push to Firebase if this tokens does NOT POLL_ONLY.
                fb_gateway = create_sms_instance(fb_identifier)
                registration_url = get_action_values_from_options(
                    SCOPE.ENROLL,
                    PUSH_ACTION.REGISTRATION_URL,
                    options=options)
                pem_privkey = self.get_tokeninfo(PRIVATE_KEY_SERVER)
                smartphone_data = _build_smartphone_data(
                    self.token.serial, challenge, registration_url,
                    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 and a warning.
            if not res:
                log.warning(
                    u"Failed to submit message to Firebase service for token {0!s}."
                    .format(self.token.serial))
                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, reply_dict
Exemplo n.º 57
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, reply_dict)
        :rtype: tuple

        The return tuple builds up like this:
        ``bool`` if submit was successful;
        ``message`` which is displayed in the JSON response;
        additional challenge ``reply_dict``, which are displayed in the JSON challenges response.
        """
        options = options or {}
        message = 'Please answer the challenge'
        attributes = {}

        # Get ValidityTime=120s. Maybe there is a OCRAChallengeValidityTime...
        validity = int(get_from_config('DefaultChallengeValidityTime', 120))
        tokentype = self.get_tokentype().lower()
        lookup_for = tokentype.capitalize() + 'ChallengeValidityTime'
        validity = int(get_from_config(lookup_for, validity))

        # Get the OCRASUITE from the token information
        ocrasuite = self.get_tokeninfo("ocrasuite") or OCRA_DEFAULT_SUITE

        challenge = options.get("challenge")
        # TODO: we could add an additional parameter to hash the challenge
        # cleartext -> sha1
        if not challenge:
            # If no challenge is given in the Request, we create a random
            # challenge based on the OCRA-SUITE
            os = OCRASuite(ocrasuite)
            challenge = os.create_challenge()
        else:
            # Add a random challenge
            if options.get("addrandomchallenge"):
                challenge += get_alphanum_str(
                    int(options.get("addrandomchallenge")))
            attributes["original_challenge"] = challenge
            attributes["qrcode"] = create_img(challenge)
            if options.get("hashchallenge", "").lower() == "sha256":
                challenge = hexlify_and_unicode(
                    hashlib.sha256(to_bytes(challenge)).digest())
            elif options.get("hashchallenge", "").lower() == "sha512":
                challenge = hexlify_and_unicode(
                    hashlib.sha512(to_bytes(challenge)).digest())
            elif options.get("hashchallenge"):
                challenge = hexlify_and_unicode(
                    hashlib.sha1(to_bytes(challenge)).digest())

        # Create the challenge in the database
        db_challenge = Challenge(self.token.serial,
                                 transaction_id=None,
                                 challenge=challenge,
                                 data=None,
                                 session=None,
                                 validitytime=validity)
        db_challenge.save()

        attributes["challenge"] = challenge
        reply_dict = {"attributes": attributes}

        return True, message, db_challenge.transaction_id, reply_dict
Exemplo n.º 58
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.
        """
        res = False
        options = options or {}
        message = get_action_values_from_options(
            SCOPE.AUTH, ACTION.CHALLENGETEXT,
            options) or DEFAULT_CHALLENGE_TEXT

        sslverify = get_action_values_from_options(
            SCOPE.AUTH, PUSH_ACTION.SSL_VERIFY, options) or "1"
        sslverify = getParam({"sslverify": sslverify},
                             "sslverify",
                             allowed_values=["0", "1"],
                             default="1")

        attributes = None
        data = None
        challenge = b32encode_and_unicode(geturandom())
        fb_identifier = self.get_tokeninfo(PUSH_ACTION.FIREBASE_CONFIG)
        if fb_identifier:
            # We send the challenge to the Firebase service
            fb_gateway = create_sms_instance(fb_identifier)
            url = fb_gateway.smsgateway.option_dict.get(
                FIREBASE_CONFIG.REGISTRATION_URL)
            message_on_mobile = get_action_values_from_options(
                SCOPE.AUTH, PUSH_ACTION.MOBILE_TEXT,
                options) or DEFAULT_MOBILE_TEXT
            title = get_action_values_from_options(
                SCOPE.AUTH, PUSH_ACTION.MOBILE_TITLE, options) or "privacyIDEA"
            smartphone_data = {
                "nonce": challenge,
                "question": message_on_mobile,
                "serial": self.token.serial,
                "title": title,
                "sslverify": sslverify,
                "url": url
            }
            # Create the signature.
            # value to string
            sign_string = u"{nonce}|{url}|{serial}|{question}|{title}|{sslverify}".format(
                **smartphone_data)

            pem_privkey = self.get_tokeninfo(PRIVATE_KEY_SERVER)
            privkey_obj = serialization.load_pem_private_key(
                to_bytes(pem_privkey), None, default_backend())

            # Sign the data with PKCS1 padding. Not all Androids support PSS padding.
            signature = privkey_obj.sign(sign_string.encode("utf8"),
                                         padding.PKCS1v15(), hashes.SHA256())
            smartphone_data["signature"] = b32encode_and_unicode(signature)

            res = fb_gateway.submit_message(
                self.get_tokeninfo("firebase_token"), smartphone_data)
            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."
            )

        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()
        return True, message, db_challenge.transaction_id, attributes
Exemplo n.º 59
0
def before_request():
    """
    This is executed before the request.

    user_required checks if there is a logged in admin or user

    The checks for ONLY admin are preformed in api/system.py
    """
    # remove session from param and gather all parameters, either
    # from the Form data or from JSON in the request body.
    ensure_no_config_object()
    request.all_data = get_all_params(request.values, request.data)
    if g.logged_in_user.get("role") == "user":
        # A user is calling this API. First thing we do is restricting the user parameter.
        # ...to restrict token view, audit view or token actions.
        request.all_data["user"] = g.logged_in_user.get("username")
        request.all_data["realm"] = g.logged_in_user.get("realm")

    try:
        request.User = get_user_from_param(request.all_data)
        # overwrite or set the resolver parameter in case of a logged in user
        if g.logged_in_user.get("role") == "user":
            request.all_data["resolver"] = request.User.resolver
    except AttributeError:
        # Some endpoints do not need users OR e.g. the setPolicy endpoint
        # takes a list as the userobject
        request.User = None
    except UserError:
        # In cases like the policy API, the parameter "user" is part of the
        # policy and will not resolve to a user object
        request.User = User()

    g.policy_object = PolicyClass()
    g.audit_object = getAudit(current_app.config)
    g.event_config = EventConfiguration()
    # access_route contains the ip adresses of all clients, hops and proxies.
    g.client_ip = get_client_ip(request,
                                get_from_config(SYSCONF.OVERRIDECLIENT))
    privacyidea_server = current_app.config.get("PI_AUDIT_SERVERNAME") or \
                         request.host
    # Already get some typical parameters to log
    serial = getParam(request.all_data, "serial")
    if serial:
        tokentype = get_token_type(serial)
    else:
        tokentype = None

    if request.User:
        audit_username = request.User.login
        audit_realm = request.User.realm
        audit_resolver = request.User.resolver
    else:
        audit_realm = getParam(request.all_data, "realm")
        audit_resolver = getParam(request.all_data, "resolver")
        audit_username = getParam(request.all_data, "user")

    g.audit_object.log({
        "success":
        False,
        "serial":
        serial,
        "user":
        audit_username,
        "realm":
        audit_realm,
        "resolver":
        audit_resolver,
        "token_type":
        tokentype,
        "client":
        g.client_ip,
        "client_user_agent":
        request.user_agent.browser,
        "privacyidea_server":
        privacyidea_server,
        "action":
        "{0!s} {1!s}".format(request.method, request.url_rule),
        "action_detail":
        "",
        "info":
        ""
    })

    if g.logged_in_user.get("role") == "admin":
        # An administrator is calling this API
        g.audit_object.log({"administrator": g.logged_in_user.get("username")})
Exemplo n.º 60
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
                if is_true(get_from_config("sms.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
            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