示例#1
0
    def __after__(response):
        '''
        __after__ is called after every action

        :param response: the previously created response - for modification
        :return: return the response
        '''

        if request_context.get('reponse_redirect', False):
            # FIXME: does this really do a redirect???
            return response

        param = request.params
        action = request_context['action']

        try:
            if c.audit['action'] in ['selfservice/index']:
                if isSelfTest():
                    log.debug("[__after__] Doing selftest!")

                    if "selftest_user" in param:
                        (c.user, _foo,
                         c.realm) = param["selftest_user"].rpartition('@')
                    else:
                        c.realm = ""
                        c.user = "******"
                        env = request.environ
                        uuser = env.get('REMOTE_USER')
                        if uuser is not None:
                            (c.user, _foo, c.realm) = uuser.rpartition('@')

                log.debug("[__after__] authenticating as %s in realm %s!" %
                          (c.user, c.realm))

                c.audit['user'] = c.user
                c.audit['realm'] = c.realm
                c.audit['success'] = True

                if 'serial' in param:
                    c.audit['serial'] = param['serial']
                    c.audit['token_type'] = getTokenType(param['serial'])

                audit = config.get('audit')
                audit.log(c.audit)

            return response

        except flap.HTTPUnauthorized as acc:
            # the exception, when an abort() is called if forwarded
            log.exception("[__after__::%r] webob.exception %r" % (action, acc))
            Session.rollback()
            Session.close()
            # FIXME: verify that this really works
            raise acc

        except Exception as e:
            log.exception("[__after__] failed with error: %r" % e)
            Session.rollback()
            Session.close()
            return sendError(response, e, context='after')
示例#2
0
    def __before__(self, **params):
        """
        __before__ is called before every action

        :param params: list of named arguments
        :return: -nothing- or in case of an error a Response
                created by sendError with the context info 'before'
        """

        action = request_context["action"]

        try:

            g.audit["success"] = False

            g.audit["client"] = get_client(request)

            # Session handling
            check_session(request)

            audit = config.get("audit")
            request_context["Audit"] = audit
            checkAuthorisation(scope="monitoring", method=action)

            return

        except Exception as exception:
            log.error(exception)
            db.session.rollback()
            return sendError(response, exception, context="before")
示例#3
0
    def __before__(self, **params):
        """
        __before__ is called before every action

        :param params: list of named arguments
        :return: -nothing- or in case of an error a Response
                created by sendError with the context info 'before'
        """

        action = request_context['action']

        try:

            c.audit = request_context['audit']
            c.audit['success'] = False

            c.audit['client'] = get_client(request)

            # Session handling
            check_session(request)

            audit = config.get('audit')
            request_context['Audit'] = audit
            checkAuthorisation(scope='monitoring', method=action)

            return

        except Exception as exception:
            log.exception(exception)
            Session.rollback()
            Session.close()
            return sendError(response, exception, context='before')
示例#4
0
 def tokenview(self):
     '''
     This is the template for the token TAB
     '''
     c.title = "LinOTP Management"
     c.tokenArray = []
     c.getotp_active = config.get("linotpGetotp.active", "False") == "True"
     return render('/manage/tokenview.mako')
示例#5
0
    def define_sql_resolver(self, name, params=None, user_mapping=None):
        """
        create sql useridresolver
        """

        engine = create_engine(config.get('DATABASE_URI'))
        db_url = engine.url

        server = db_url.host
        if db_url.port:
            server = "%s:%s" % (db_url.host, db_url.port)

        if not user_mapping:
            user_mapping = {}
        usermap = {
                "userid": "id",
                "username": "******",
                "phone": "telephoneNumber",
                "mobile": "mobile",
                "email": "mail",
                "surname": "sn",
                "givenname": "givenName",
                "password": "******",
                "salt": "salt"
                }
        user_mapping.update(usermap)

        if not params:
            params = {}

        resolver_def = {
                'name': name,

                'Server': server,
                'Database': db_url.database,
                'Driver': db_url.drivername,
                'User': db_url.username,
                # 'Password': db_url.password,

                'Map': json.dumps(usermap),
                'Where': '',
                'Encoding': '',
                'Limit': '40',
                'Table': 'usertable',
                'type': 'sqlresolver',
                'Port': '3306',
                'conParams': ''}

        resolver_def.update(params)
        resolver_def['name'] = name

        response = self.make_system_request('setResolver',
                                            params=resolver_def)

        return response, resolver_def
示例#6
0
    def help(self, id=None):
        """
        This downloads the Manual

        The filename will be the 3. part,ID
        https://172.16.200.6/manage/help/somehelp.pdf
        The file is downloaded through Flask!

        """

        try:
            directory = config.get(
                "linotpManual.Directory", "/usr/share/doc/linotp"
            )
            default_filename = config.get(
                "linotpManual.File", "LinOTP_Manual-en.pdf"
            )
            mimetype = "application/pdf"
            headers = []

            # FIXME: Compression is better done using
            # `Content-Encoding` (ideally farther up the WSGI stack).

            # if not id:
            #     id = default_filename + ".gz"
            #     mimetype = "application/x-gzip"  # iffy

            id = id or default_filename

            r = flask.send_file(
                "%s/%s" % (directory, id),
                mimetype=mimetype,
                as_attachment=True,
                attachment_filename=default_filename,
            )
            db.session.commit()
            return r

        except Exception as exx:
            log.error("[help] Error loading helpfile: %r", exx)
            db.session.rollback()
            return sendError(response, exx)
示例#7
0
    def __after__(response):
        '''
        __after__ is called after every action

        :param response: the previously created response - for modification
        :return: return the response
        '''

        audit = config.get('audit')
        audit.log(c.audit)
        return response
示例#8
0
def check_session(request, scope='admin'):
    '''
    This function checks the session cookie and compares it to
    the session parameter

    :param request: the request object
    :param scope: by default the admin scope, but used to as well
                  for the scope helpdesk with the helpdesk_session
                  cookie name

    :return: boolean
    '''

    if isSelfTest():
        return

    # check if the client is in the allowed IP range
    no_session_clients = []
    for no_session_client in config.get("linotpNoSessionCheck", "").split(","):
        no_session_clients.append(no_session_client.strip())

    client = request.environ.get('REMOTE_ADDR', None)
    log.debug("[check_session] checking %s in %s"
              % (client, no_session_clients))
    for network in no_session_clients:
        if not network:
            continue
        try:
            if netaddr.IPAddress(client) in netaddr.IPNetwork(network):
                log.debug("skipping session check since client"
                          " %s in allowed: %s" % (client, no_session_clients))
                return
        except Exception as ex:
            log.warning("misconfiguration in linotpNoSessionCheck: "
                        "%r - %r" % (network, ex))

    cookie = request.cookies.get(scope + '_session')
    session = get_request_param(request, 'session')
    # doing any other request, we need to check the session!
    log.debug("[check_session]: session: %s" % session)
    log.debug("[check_session]: cookie:  %s" % cookie)
    if session is None or session == "" or session != cookie:
        log.error("The request did not pass a valid session!")
        abort(401, "You have no valid session!")

    cookie = request.cookies.get(scope + '_session')
    session = get_request_param(request, 'session')
    # doing any other request, we need to check the session!
    log.debug("[check_session]: session: %s" % session)
    log.debug("[check_session]: cookie:  %s" % cookie)
    if session is None or session == "" or session != cookie:
        log.error("The request did not pass a valid session!")
        abort(401, "You have no valid session!")
示例#9
0
    def define_sql_resolver(self, name, params=None, user_mapping=None):
        """
        create sql useridresolver
        """

        engine = create_engine(config.get("DATABASE_URI"))
        db_url = engine.url

        server = db_url.host
        if db_url.port:
            server = "%s:%s" % (db_url.host, db_url.port)

        if not user_mapping:
            user_mapping = {}
        usermap = {
            "userid": "id",
            "username": "******",
            "phone": "telephoneNumber",
            "mobile": "mobile",
            "email": "mail",
            "surname": "sn",
            "givenname": "givenName",
            "password": "******",
            "salt": "salt",
        }
        user_mapping.update(usermap)

        if not params:
            params = {}

        resolver_def = {
            "name": name,
            "Server": server,
            "Database": db_url.database,
            "Driver": db_url.drivername,
            "User": db_url.username,
            # 'Password': db_url.password,
            "Map": json.dumps(usermap),
            "Where": "",
            "Encoding": "",
            "Limit": "40",
            "Table": "usertable",
            "type": "sqlresolver",
            "Port": "3306",
            "conParams": "",
        }

        resolver_def.update(params)
        resolver_def["name"] = name

        response = self.make_system_request("setResolver", params=resolver_def)

        return response, resolver_def
示例#10
0
    def test_license(self):
        old_lic = None
        old_sig = None
        try:
            old_lic, old_sig = self.getCurrentLicense()
        except InvalidLicenseException as exx:
            if (str(exx) != "Support not available, your product is "
                    "unlicensed"):
                raise exx

        try:
            # Load the license file...
            licfile = config.get('monitoringTests.licfile', '')

            if not licfile:
                self.skipTest('Path to test license file is not configured, '
                              'check your configuration (test.ini)!')

            lic_dict, lic_sig = readLicenseInfo(licfile)

            self.installLicense(licfile)

            self.create_token(serial='0031')
            self.create_token(serial='0032', user='******')
            self.create_token(serial='0033', realm='mydefrealm')
            self.create_token(serial='0034', realm='myotherrealm')
            self.create_token(serial='0035',
                              realm='myotherrealm',
                              active=False)
            self.create_token(serial='0036',
                              realm='myotherrealm',
                              user='******',
                              active=False)

            response = self.make_authenticated_request(controller='monitoring',
                                                       action='license',
                                                       params={})
            resp = json.loads(response.body)
            value = resp.get('result').get('value')
            assert value.get('token-num') == \
                             int(lic_dict.get('token-num')), \
                             response
            token_left = int(lic_dict.get('token-num')) - 4
            assert value.get('token-left') == token_left, response

        finally:
            # restore previous license...
            if old_lic and old_sig:
                self.setCurrentLicense(old_lic, old_sig)

        return
示例#11
0
    def test_update_critical_data_sql(self):
        """
        test: it's not possible to define a resolver w. critical changes
        """

        #
        # define resolver SqlX w. the required Password
        if config.get('SQLALCHEMY_DATABASE_URI').startswith('sqlite://'):
            # We cannot define a sqlite databse with a password, so skip
            # this test
            pytest.skip("not possible with sqlite")

        params = {
            "Password": "******",
        }
        response, params = self.define_sql_resolver('SqlX', params=params)
        assert '"status": true,' in response, response

        #
        # rename resolver SqlX to SqlZ with critical changes
        # w.o. password will fail

        params = {
            'previous_name': 'SqlX',
            'User': '******',
        }

        response, params = self.define_sql_resolver('SqlZ', params=params)
        assert '"status": false,' in response, response

        response = self.make_system_request('getResolvers')
        assert 'SqlZ' not in response, response
        assert 'SqlX' in response, response

        #
        # rename resolver SqlX to SqlZ with critical changes
        # w. password will have success

        params = {
            'previous_name': 'SqlX',
            'User': '******',
            'Password': '******'
        }

        response, params = self.define_sql_resolver('SqlZ', params=params)
        assert '"status": true,' in response, response

        response = self.make_system_request('getResolvers')
        assert 'SqlX' not in response, response
        assert 'SqlZ' in response, response
示例#12
0
    def __after__(response):
        '''
        __after__ is called after every action

        :param response: the previously created response - for modification
        :return: return the response
        '''

        audit = config.get('audit')

        c.audit['administrator'] = getUserFromRequest(request).get("login")
        audit.log(c.audit)

        return response
示例#13
0
def uencode(value):
    """
    unicode escape the value - required to support non-unicode
    databases
    :param value: string to be escaped
    :return: unicode encoded value
    """
    ret = value

    if (env.get("linotp.uencode_data", "").lower() == 'true'):
        try:
            ret = json.dumps(value)[1:-1]
        except Exception as exx:
            log.exception("Failed to encode value %r. Exception was %r"
                          % (value, exx))

    return ret
示例#14
0
    def __after__(response):
        '''
        __after__ is called after every action

        :param response: the previously created response - for modification
        :return: return the response
        '''

        c.audit['administrator'] = getUserFromRequest(request).get("login")
        if 'serial' in request.params:
            serial = request.params['serial']
            c.audit['serial'] = serial
            c.audit['token_type'] = getTokenType(serial)

        audit = config.get('audit')
        audit.log(c.audit)

        return response
示例#15
0
def get_imprint(realm):
    '''
    This function returns the imprint for a certain realm.
    This is just the contents of the file <realm>.imprint in the directory
    <imprint_directory>
    '''
    res = ""
    realm = realm.lower()
    directory = config.get("linotp.imprint_directory", "/etc/linotp/imprint")
    filename = "%s/%s.imprint" % (directory, realm)
    try:
        pass
        f = open(filename)
        res = f.read()
        f.close()
    except Exception as e:
        log.info("[get_imprint] can not read imprint file: %s. (%r)" %
                 (filename, e))

    return res
示例#16
0
    def __before__(self, **params):
        """
        __before__ is called before every action

        Here we see, what action is to be called and check the authorization

        :param params: list of named arguments
        :return: -nothing- or in case of an error a Response
                created by sendError with the context info 'before'
        """

        action = request_context['action']

        try:

            c.audit = request_context['audit']
            c.audit['success'] = False
            c.audit['client'] = get_client(request)
            if action != "check_t":
                check_session(request)

            audit = config.get('audit')
            request_context['Audit'] = audit
            return

        except flap.HTTPUnauthorized as acc:

            # the exception, when an abort() is called if forwarded

            log.exception("[__before__::%r] webob.exception %r" %
                          (action, acc))

            Session.rollback()
            Session.close()
            raise acc

        except Exception as exx:
            log.exception("[__before__::%r] exception %r" % (action, exx))
            Session.rollback()
            Session.close()
            return sendError(response, exx, context='before')
示例#17
0
文件: manage.py 项目: smartree/LinOTP
    def __after__(response):
        '''
        __after__ is called after every action

        :param response: the previously created response - for modification
        :return: return the response
        '''

        if c.audit['action'] in ['manage/tokenview_flexi',
                                 'manage/userview_flexi' ]:
            c.audit['administrator'] = getUserFromRequest(request).get("login")
            if 'serial' in request.params:
                serial = request.params['serial']
                c.audit['serial'] = serial
                c.audit['token_type'] = getTokenType(serial)

            c.audit['action_detail'] += linotp.lib.audit.base.get_token_num_info()
            audit = config.get('audit')
            audit.log(c.audit)

        return response
示例#18
0
    def __before__(self, **params):
        """
        __before__ is called before every action

        we check if the client cert was valid by looking for
        the existance of a CGI environment variable. For apache
        this is SSL_CLIENT_S_DN_CN. To support other servers we
        read the name of the variable from the config

        :param params: list of named arguments
        :return: -nothing- or in case of an error a Response
                created by sendError with the context info 'before'
        """

        env_var = config.get('MAINTENANCE_VERIFY_CLIENT_ENV_VAR', False)

        if env_var:

            client_cert = request.environ.get(env_var)

            if client_cert is None:
                abort(401)
示例#19
0
    def __before__(self, **params):
        """
        __before__ is called before every action

        :param params: list of named arguments
        :return: -nothing- or in case of an error a Response
                created by sendError
        """

        action = request_context['action']

        try:
            c.audit = request_context['audit']
            c.audit['client'] = get_client(request)
            audit = config.get('audit')
            request_context['Audit'] = audit
            return

        except Exception as exx:
            log.exception("[__before__::%r] exception %r" % (action, exx))
            Session.rollback()
            Session.close()
            return sendError(response, exx, context='before')
示例#20
0
    def __after__(response):
        '''
        __after__ is called after every action

        :param response: the previously created response - for modification
        :return: return the response
        '''

        try:
            c.audit['administrator'] = getUserFromRequest(request).get('login')

            audit = config.get('audit')
            audit.log(c.audit)
            Session.commit()
            return response

        except Exception as exception:
            log.exception(exception)
            Session.rollback()
            return sendError(response, exception, context='after')

        finally:
            Session.close()
示例#21
0
文件: util.py 项目: soitun/LinOTP
def check_session(request):
    """
    This function checks if the client is in the allowed
    IP range. The session cookie is no longer checked
    here because flask-jwt-extended does this now in
    BaseController::jwt_check.

    :param request: the request object

    :return: boolean
    """

    # check if the client is in the allowed IP range
    no_session_clients = []
    for no_session_client in config.get("linotpNoSessionCheck", "").split(","):
        no_session_clients.append(no_session_client.strip())

    client = request.environ.get("REMOTE_ADDR", None)
    log.debug("[check_session] checking %s in %s", client, no_session_clients)
    for network in no_session_clients:
        if not network:
            continue
        try:
            if netaddr.IPAddress(client) in netaddr.IPNetwork(network):
                log.debug(
                    "skipping session check since client %s in allowed: %s",
                    client,
                    no_session_clients,
                )
                return
        except Exception as ex:
            log.warning(
                "misconfiguration in linotpNoSessionCheck: %r - %r",
                network,
                ex,
            )
示例#22
0
文件: vasco.py 项目: Jess103/LinOTP
def init_vasco():
    """
    Vasco library initialiser
    """
    global vasco_dll
    global vasco_libs

    # get the Vacman Controller lib
    fallbacks = ["/opt/vasco/Vacman_Controller/lib/libaal2sdk.so"]

    vasco_lib = config.get("linotpImport.vasco_dll")
    if not vasco_lib:
        log.info("Missing linotpImport.vasco_dll in config file")
    else:
        vasco_libs.append(vasco_lib)

    vasco_libs.extend(fallbacks)
    for vasco_lib in vasco_libs:
        try:
            log.debug("loading vasco lib %r", vasco_lib)
            vasco_dll = CDLL(vasco_lib)
            break
        except Exception as exx:
            log.info("cannot load vasco library: %r", exx)
示例#23
0
    def index(self):
        '''
        This is the main function of the management web UI
        '''

        try:
            c.debug = bool(config.get('debug', False))
            c.title = "LinOTP Management"
            admin_user = getUserFromRequest(request)

            if 'login' in admin_user:
                c.admin = admin_user['login']

            log.debug("[index] importers: %s" % IMPORT_TEXT)
            c.importers = IMPORT_TEXT
            help_version = c.version[:c.version.find('.')]
            c.help_url = config.get('HELP_URL').format(help_version)

            # -------------------------------------------------------------- --

            # check for support of setting admin password

            c.admin_can_change_password = False
            if ('linotpadmin.user' in config
                    and 'linotpadmin.password' in config):
                c.admin_can_change_password = True

            # -------------------------------------------------------------- --

            # add render info for token type config
            confs = _getTokenTypeConfig('config')
            token_config_tab = {}
            token_config_div = {}
            for conf in confs:
                tab = ''
                div = ''
                try:
                    # loc = conf +'_token_settings'
                    tab = confs.get(conf).get('title')
                    # tab = '<li ><a href=#'+loc+'>'+tab+'</a></li>'

                    div = confs.get(conf).get('html')
                    # div = +div+'</div>'
                except Exception as e:
                    log.debug(
                        '[index] no config info for token type %s  (%r)' %
                        (conf, e))

                if tab is not None and div is not None and len(
                        tab) > 0 and len(div) > 0:
                    token_config_tab[conf] = tab
                    token_config_div[conf] = div

            c.token_config_tab = token_config_tab
            c.token_config_div = token_config_div

            #  add the enrollment fragments from the token definition
            #  tab: <option value="ocra">${_("OCRA - challenge/response Token")}</option>
            #  div: "<div id='"+ tt + "'>"+enroll+"</div>"
            enrolls = _getTokenTypeConfig('init')

            token_enroll_tab = {}
            token_enroll_div = {}
            for conf in enrolls:
                tab = ''
                div = ''
                try:
                    tab = enrolls.get(conf).get('title')
                    div = enrolls.get(conf).get('html')
                except Exception as e:
                    log.debug(
                        '[index] no enrollment info for token type %s  (%r)' %
                        (conf, e))

                if tab is not None and div is not None and len(
                        tab) > 0 and len(div) > 0:
                    token_enroll_tab[conf] = tab
                    token_enroll_div[conf] = div

            c.token_enroll_tab = token_enroll_tab
            c.token_enroll_div = token_enroll_div

            c.tokentypes = _getTokenTypes()

            # Use HTTP_X_FORWARDED_HOST in preference to HTTP_HOST
            # in case we're running behind a reverse proxy
            http_host = request.environ.get("HTTP_X_FORWARDED_HOST", '')
            if not http_host:
                http_host = request.environ.get("HTTP_HOST")
            url_scheme = request.environ.get("wsgi.url_scheme")
            c.logout_url = "%s://log-me-out:fake@%s/manage/logout" % (
                url_scheme, http_host)

            Session.commit()
            ren = render('/manage/manage-base.mako')
            return ren

        except PolicyException as pe:
            log.exception("[index] Error during checking policies: %r" % pe)
            Session.rollback()
            return sendError(response, str(pe), 1)

        except Exception as ex:
            log.exception("[index] failed! %r" % ex)
            Session.rollback()
            raise

        finally:
            Session.close()
示例#24
0
    def getotp(self):
        '''
        This function is used to retrieve the current otp value for a given
        user or a given serial. If the user has more than one token, the list
        of the tokens is returend.

        method:
            gettoken/getotp

        arguments:
            user    - username / loginname
            realm   - additional realm to match the user to a useridresolver
            serial  - the serial number of the token
            curTime - used ONLY for internal testing: datetime.datetime object

        returns:
            JSON response
        '''

        getotp_active = config.get("GETOTP_ENABLED")
        if not getotp_active:
            return sendError(response, "getotp is not activated.", 0)

        param = self.request_params
        ret = {}
        res = -1
        otpval = ""
        passw = ""
        serials = []

        try:

            serial = getParam(param, "serial", optional)
            user = getUserFromParam(param)
            curTime = getParam(param, "curTime", optional)

            g.audit['user'] = user.login
            if "" != user.login:
                g.audit['realm'] = user.realm or getDefaultRealm()

            if serial:
                log.debug("[getotp] retrieving OTP value for token %s", serial)

            elif user.login:
                log.debug(
                    "[getotp] retrieving OTP value for token for user "
                    "%s@%s", user.login, user.realm)

                toks = getTokens4UserOrSerial(user, serial)
                tokennum = len(toks)

                if tokennum > 1:
                    log.debug("[getotp] The user has more than one token."
                              "Returning the list of serials")
                    res = -3
                    for token in toks:
                        serials.append(token.getSerial())
                elif 1 == tokennum:
                    serial = toks[0].getSerial()
                    log.debug(
                        "[getotp] retrieving OTP for token %s for user"
                        " %s@%s", serial, user.login, user.realm)
                else:
                    log.debug("[getotp] no token found for user %s@%s",
                              user.login, user.realm)
                    res = -4
            else:
                res = -5

            # if a serial was given or a unique serial could be
            # received from the given user.

            if serial:
                max_count = checkPolicyPre('gettoken', 'max_count', param)
                log.debug("[getmultiotp] max_count policy: %s", max_count)
                if max_count <= 0:
                    return sendError(
                        response, "The policy forbids receiving"
                        " OTP values for the token %s in "
                        "this realm" % serial, 1)

                (res, pin, otpval, passw) = getOtp(serial, curTime=curTime)

            g.audit['success'] = True

            if int(res) < 0:
                ret['result'] = False
                if -1 == otpval:
                    ret['description'] = "No Token with this serial number"
                if -2 == otpval:
                    ret['description'] = ("This Token does not support the"
                                          " getOtp function")
                if -3 == otpval:
                    ret['description'] = "The user has more than one token"
                    ret['serials'] = serials
                if -4 == otpval:
                    ret['description'] = "No Token found for this user"
                if -5 == otpval:
                    ret['description'] = ("you need to provide a user or "
                                          "a serial")
            else:
                ret['result'] = True
                ret['otpval'] = otpval
                ret['pin'] = pin
                ret['pass'] = passw

            db.session.commit()
            return sendResult(response, ret, 0)

        except PolicyException as pe:
            log.exception("[getotp] gettoken/getotp policy failed: %r", pe)
            db.session.rollback()
            return sendError(response, str(pe), 1)

        except Exception as exx:
            log.exception("[getotp] gettoken/getotp failed: %r", exx)
            db.session.rollback()
            return sendError(response, "gettoken/getotp failed: %s" % exx, 0)
示例#25
0
    def getmultiotp(self):
        '''
        This function is used to retrieve multiple otp values for a given user
        or a given serial. If the user has more than one token, the list of
        the tokens is returend.

        method:
            gettoken/getmultiotp

        arguments:
            serial  - the serial number of the token
            count   - number of otp values to return
            curTime - used ONLY for internal testing: datetime.datetime object

        returns:
            JSON response
        '''

        getotp_active = config.get("GETOTP_ENABLED")
        if not getotp_active:
            return sendError(response, "getotp is not activated.", 0)

        param = self.request_params
        ret = {}

        try:
            serial = getParam(param, "serial", required)
            count = int(getParam(param, "count", required))
            curTime = getParam(param, "curTime", optional)
            view = getParam(param, "view", optional)

            r1 = checkPolicyPre('admin', 'getotp', param)
            log.debug("[getmultiotp] admin-getotp policy: %s", r1)

            max_count = checkPolicyPre('gettoken', 'max_count', param)
            log.debug("[getmultiotp] maxcount policy: %s", max_count)
            if count > max_count:
                count = max_count

            log.debug("[getmultiotp] retrieving OTP value for token %s",
                      serial)
            ret = get_multi_otp(serial, count=int(count), curTime=curTime)
            ret["serial"] = serial

            g.audit['success'] = True
            db.session.commit()

            if view:
                c.ret = ret
                return render('/manage/multiotp_view.mako').decode('utf-8')
            else:
                return sendResult(response, ret, 0)

        except PolicyException as pe:
            log.exception("[getotp] gettoken/getotp policy failed: %r", pe)
            db.session.rollback()
            return sendError(response, str(pe), 1)

        except Exception as exx:
            log.exception("[getmultiotp] gettoken/getmultiotp failed: %r", exx)
            db.session.rollback()
            return sendError(response, "gettoken/getmultiotp failed: %r" % exx,
                             0)
示例#26
0
 def __init__(self):
     self.engine = create_engine(config.get("DATABASE_URI"))
示例#27
0
    def __before__(self, **params):
        """
        __before__ is called before every action

        This is the authentication to self service. If you want to do
        ANYTHING with the selfservice, you need to be authenticated. The
        _before_ is executed before any other function in this controller.

        :param params: list of named arguments
        :return: -nothing- or in case of an error a Response
                created by sendError with the context info 'before'
        """

        action = request_context['action']
        self.redirect = None

        try:
            c.version = get_version()
            c.licenseinfo = get_copyright_info()

            c.audit = request_context['audit']
            c.audit['success'] = False
            self.client = get_client(request)
            c.audit['client'] = self.client

            audit = config.get('audit')
            request_context['Audit'] = audit

            # -------------------------------------------------------------- --

            # handle requests which dont require authetication

            if action in ['logout', 'custom_style']:
                return

            # -------------------------------------------------------------- --

            # get the authenticated user

            auth_type, auth_user, auth_state = get_auth_user(request)

            # -------------------------------------------------------------- --

            # handle not authenticated requests

            if not auth_user or auth_type not in ['user_selfservice']:

                if action in ['login']:
                    return

                if action in ['index']:
                    self.redirect = True
                    return redirect(
                        url(controller='selfservice', action='login'))

                else:
                    Unauthorized('No valid session')

            # -------------------------------------------------------------- --

            # handle authenticated requests

            # there is only one special case, which is the login that
            # could be forwarded to the index page

            if action in ['login']:
                if auth_state != 'authenticated':
                    return

                self.redirect = True
                return redirect(url(controller='selfservice', action='index'))

            # -------------------------------------------------------------- --

            # in case of user_selfservice, an unauthenticated request should always go to login
            if auth_user and auth_type is 'user_selfservice' \
                    and auth_state is not 'authenticated':
                self.redirect = True
                return redirect(url(controller='selfservice', action='login'))

            # futher processing with the authenticated user

            if auth_state != 'authenticated':
                Unauthorized('No valid session')

            c.user = auth_user.login
            c.realm = auth_user.realm
            self.authUser = auth_user

            # -------------------------------------------------------------- --

            # authenticated session verification

            if auth_type == 'user_selfservice':

                # checking the session only for not_form_access actions
                if action not in self.form_access_methods:

                    valid_session = check_session(request, auth_user,
                                                  self.client)

                    if not valid_session:
                        c.audit['action'] = request.path[1:]
                        c.audit['info'] = "session expired"
                        audit.log(c.audit)

                        Unauthorized('No valid session')

            # -------------------------------------------------------------- --

            c.imprint = get_imprint(c.realm)

            c.tokenArray = []

            c.user = self.authUser.login
            c.realm = self.authUser.realm

            # only the defined actions should be displayed
            # - remark: the generic actions like enrollTT are allready approved
            #   to have a rendering section and included
            actions = getSelfserviceActions(self.authUser)
            c.actions = actions
            for policy in actions:
                if policy:
                    if "=" not in policy:
                        c.__setattr__(policy, -1)
                    else:
                        (name, val) = policy.split('=')
                        val = val.strip()
                        # try if val is a simple numeric -
                        # w.r.t. javascript evaluation
                        try:
                            nval = int(val)
                        except ValueError:
                            nval = val
                        c.__setattr__(name.strip(), nval)

            c.dynamic_actions = add_dynamic_selfservice_enrollment(
                config, c.actions)

            # we require to establish all token local defined
            # policies to be initialiezd
            additional_policies = add_dynamic_selfservice_policies(
                config, actions)
            for policy in additional_policies:
                c.__setattr__(policy, -1)

            c.otplen = -1
            c.totp_len = -1

            c.pin_policy = _get_auth_PinPolicy(user=self.authUser)

        except (flap.HTTPUnauthorized, flap.HTTPForbidden) as acc:
            # the exception, when an abort() is called if forwarded
            log.info("[__before__::%r] webob.exception %r" % (action, acc))
            Session.rollback()
            Session.close()
            raise acc

        except Exception as e:
            log.exception("[__before__] failed with error: %r" % e)
            Session.rollback()
            Session.close()
            return sendError(response, e, context='before')
示例#28
0
 def __init__(self):
     self.engine = create_engine(config.get('SQLALCHEMY_DATABASE_URI'))
示例#29
0
    def create_challenge_url(self,
                             transaction_id,
                             content_type,
                             callback_url='',
                             message=None,
                             login=None,
                             host=None):
        """
        creates a challenge url (looking like lseqr://push/<base64string>),
        returns the url and the unencrypted challenge data

        :param transaction_id: The transaction id generated by LinOTP

        :param content_type: One of the types CONTENT_TYPE_SIGNREQ,
            CONTENT_TYPE_PAIRING, CONTENT_TYPE_LOGIN

        :param callback_url: callback url (optional), default is
            empty string

        :param message: the transaction message, that should be signed
            by the client. Only for content type CONTENT_TYPE_SIGNREQ

        :param login: the login name of the user. Only for content type
            CONTENT_TYPE_LOGIN

        :param host: hostname of the user. Only for content type
            CONTENT_TYPE_LOGIN

        :returns: tuple (challenge_url, sig_base), with challenge_url being
            the push url and sig_base the message, that is used for
            the client signature
        """

        serial = self.getSerial()

        # ------------------------------------------------------------------- --

        # sanity/format checks

        if content_type not in [
                CONTENT_TYPE_SIGNREQ, CONTENT_TYPE_PAIRING, CONTENT_TYPE_LOGIN
        ]:
            raise InvalidFunctionParameter(
                'content_type', 'content_type must '
                'be CONTENT_TYPE_SIGNREQ, '
                'CONTENT_TYPE_PAIRING or '
                'CONTENT_TYPE_LOGIN.')

        # ------------------------------------------------------------------- --

        #  after the lseqr://push/ prefix the following data is encoded
        #  in urlsafe base64:

        #            ---------------------------------------------------
        #  fields   | version | user token id |  R  | ciphertext | sign |
        #            ---------------------------------------------------
        #           |          header         |          body           |
        #            ---------------------------------------------------
        #  size     |    1    |       4       |  32 |      ?     |  64  |
        #            ---------------------------------------------------
        #

        # create header

        user_token_id = self.getFromTokenInfo('user_token_id')
        data_header = struct.pack('<bI', CHALLENGE_URL_VERSION, user_token_id)

        # ------------------------------------------------------------------- --

        # create body

        r = secrets.token_bytes(32)
        R = calc_dh_base(r)

        b64_user_dsa_public_key = self.getFromTokenInfo('user_dsa_public_key')
        user_dsa_public_key = b64decode(b64_user_dsa_public_key)
        user_dh_public_key = dsa_to_dh_public(user_dsa_public_key)

        ss = calc_dh(r, user_dh_public_key)
        U = SHA256.new(ss).digest()
        zerome(ss)

        sk = U[0:16]
        nonce = U[16:32]
        zerome(U)

        # ------------------------------------------------------------------- --

        # create plaintext section

        # ------------------------------------------------------------------- --

        # generate plaintext header

        #            ------------------------------------------------
        #  fields   | content_type  | transaction_id | timestamp | ..
        #            ------------------------------------------------
        #  size     |       1       |        8       |     8     |  ?
        #            -------------------------------------------------

        transaction_id = transaction_id_to_u64(transaction_id)
        plaintext = struct.pack('<bQQ', content_type, transaction_id,
                                int(time.time()))

        # ------------------------------------------------------------------- --

        utf8_callback_url = callback_url.encode('utf8')

        # enforce max url length as specified in protocol

        if len(utf8_callback_url) > 511:
            raise InvalidFunctionParameter(
                'callback_url', 'max string '
                'length (encoded as utf8) is '
                '511')

        # ------------------------------------------------------------------- --

        # create data package depending on content type

        # ------------------------------------------------------------------- --

        if content_type == CONTENT_TYPE_PAIRING:

            #            -----------------------------------------
            #  fields   | header | serial | NUL | callback | NUL |
            #            -----------------------------------------
            #  size     |   9    |    ?   |  1  |     ?    |  1  |
            #            -----------------------------------------

            utf8_serial = serial.encode('utf8')

            if len(utf8_serial) > 63:
                raise ValueError('serial (encoded as utf8) can only be 63 '
                                 'characters long')

            plaintext += utf8_serial + b'\00' + utf8_callback_url + b'\00'

        # ------------------------------------------------------------------- --

        if content_type == CONTENT_TYPE_SIGNREQ:

            if message is None:
                raise InvalidFunctionParameter(
                    'message', 'message must be '
                    'supplied for content type '
                    'SIGNREQ')

            #            ------------------------------------------
            #  fields   | header | message | NUL | callback | NUL |
            #            ------------------------------------------
            #  size     |   9    |    ?    |  1  |     ?    |  1  |
            #            ------------------------------------------

            utf8_message = message.encode('utf8')

            # enforce max sizes specified by protocol

            if len(utf8_message) > 511:
                raise InvalidFunctionParameter(
                    'message', 'max string '
                    'length (encoded as utf8) is '
                    '511')

            plaintext += utf8_message + b'\00' + utf8_callback_url + b'\00'

        # ------------------------------------------------------------------- --

        if content_type == CONTENT_TYPE_LOGIN:

            if login is None:
                raise InvalidFunctionParameter(
                    'login', 'login must be '
                    'supplied for content type '
                    'LOGIN')
            if host is None:
                raise InvalidFunctionParameter(
                    'host', 'host must be '
                    'supplied for content type '
                    'LOGIN')

            #            -----------------------------------------------------
            #  fields   | header | login | NUL | host | NUL | callback | NUL |
            #            -----------------------------------------------------
            #  size     |   9    |   ?   |  1  |   ?  |  1  |     ?    |  1  |
            #            -----------------------------------------------------

            utf8_login = login.encode('utf8')
            utf8_host = host.encode('utf8')

            # enforce max sizes specified by protocol

            if len(utf8_login) > 127:
                raise InvalidFunctionParameter(
                    'login', 'max string '
                    'length (encoded as utf8) is '
                    '127')
            if len(utf8_host) > 255:
                raise InvalidFunctionParameter(
                    'host', 'max string '
                    'length (encoded as utf8) is '
                    '255')

            plaintext += utf8_login + b'\00'
            plaintext += utf8_host + b'\00'
            plaintext += utf8_callback_url + b'\00'

        # ------------------------------------------------------------------- --

        # encrypt inner layer

        nonce_as_int = int_from_bytes(nonce, byteorder='big')
        ctr = Counter.new(128, initial_value=nonce_as_int)
        cipher = AES.new(sk, AES.MODE_CTR, counter=ctr)
        ciphertext = cipher.encrypt(plaintext)
        unsigned_raw_data = data_header + R + ciphertext

        # ------------------------------------------------------------------- --

        # create signature

        partition = self.getFromTokenInfo('partition')
        secret_key = get_secret_key(partition)
        signature = crypto_sign_detached(unsigned_raw_data, secret_key)
        raw_data = unsigned_raw_data + signature

        protocol_id = config.get('mobile_app_protocol_id', 'lseqr')
        url = protocol_id + '://push/' + encode_base64_urlsafe(raw_data)

        return url, (signature + plaintext)
示例#30
0
文件: pairing.py 项目: soitun/LinOTP
def generate_pairing_url(
    token_type,
    partition=None,
    serial=None,
    callback_url=None,
    callback_sms_number=None,
    otp_pin_length=None,
    hash_algorithm=None,
    use_cert=False,
):
    """
    Generates a pairing url that should be sent to the client.

    Mandatory parameters:

    :param: token_type The token type for which this url is generated
        as a string (currently supported is only 'qr')

    Optional parameters:

    :param partition: A partition id that should be used during pairing.
        Partitions identitify a subspace of tokens, that share a common
        key pair. This currently defaults to the enum id of the token
        type when set to None and is reserved for future use.

    :param serial: When a token for the client was already enrolled
        (e.g. manually in the manage interface) its serial has to be
        sent to the client. When serial is not specified the client will
        receive a so-called 'anonymous pairing url' with no token data
        inside it. The token will then be created after the server
        received a pairing response from the client.

    :param callback_url: A callback URL that should be used by the client
        to sent back the pairing reponse. Please note, that this url will
        be cached by the client and used in the challenge step, if the
        challenge doesn't provide a custom url

    :param callback_sms_number: A sms number that can be used by the client
        to send back the pairing response. Typically this is used as a
        fallback for offline pairing.
        As with the callback url please note, that the number will be
        cached by the client. If you want a different number in the
        challenge step you have to send it inside the challenge as
        specified in the challenge protocol

    :param otp_pin_length: The number of digits the otp has to consist of.

    :param hash_algorithm: A string value that signifies the hash algorithm
        used in calculating the hmac. Currently the values 'sha1', 'sha256',
        'sha512' are supported. If the parameter is left out the default
        depends on the token type. qrtoken uses sha256 as default, while
        hotp/totp uses sha1.

    :param use_cert: A boolean, if a server certificate should be used
        in the pairing url

    The function can raise several exceptions:

    :raises InvalidFunctionParameter: If the string given in token_type
        doesn't match a supported token type

    :raises InvalidFunctionParameter: If the string given in hash_algorithm
        doesn't match a supported hash algorithm

    :raises InvalidFunctionParameter: If public key has a different size
        than 32 bytes

    :raises InvalidFunctionParameter: If otp_pin_length value is not between
        1 and 127

    :return: Pairing URL string
    """

    # ---------------------------------------------------------------------- --

    # check the token type

    try:
        TOKEN_TYPE = TOKEN_TYPES[token_type]
    except KeyError:
        allowed_types = ", ".join(list(TOKEN_TYPES.keys()))
        raise InvalidFunctionParameter(
            "token_type",
            "Unsupported token type %s. Supported "
            "types for pairing are: %s" % (token_type, allowed_types),
        )

    # ---------------------------------------------------------------------- --

    # initialize the flag bitmap

    flags = 0

    if not use_cert:
        flags |= FLAG_PAIR_PK
    if serial is not None:
        flags |= FLAG_PAIR_SERIAL
    if callback_url is not None:
        flags |= FLAG_PAIR_CBURL
    if callback_sms_number is not None:
        flags |= FLAG_PAIR_CBSMS
    if hash_algorithm is not None:
        flags |= FLAG_PAIR_HMAC
    if otp_pin_length is not None:
        flags |= FLAG_PAIR_DIGITS

    # ---------------------------------------------------------------------- --

    #            ---------------------------- --
    #  fields   | version | type | flags | ... |
    #            ---------------------------- --
    #  size     |    1    |  1   |   4   |  ?  |
    #            ---------------------------- --

    data = struct.pack("<bbI", PAIR_URL_VERSION, TOKEN_TYPE, flags)

    # ---------------------------------------------------------------------- --

    #            --------------------- --
    #  fields   | ... | partition | ... |
    #            --------------------- --
    #  size     |  6  |     4     |  ?  |
    #            --------------------- --

    data += struct.pack("<I", partition)

    # ---------------------------------------------------------------------- --

    #            ------------------------------ --
    #  fields   | .... | server public key | ... |
    #            ------------------------------ --
    #  size     |  10  |        32         |  ?  |
    #            ------------------------------ --

    if flags & FLAG_PAIR_PK:

        server_public_key = get_public_key(partition)

        if len(server_public_key) != 32:
            raise InvalidFunctionParameter(
                "server_public_key", "Public key must be 32 bytes long"
            )

        data += server_public_key

    # ---------------------------------------------------------------------- --

    # Depending on flags additional data may be sent. If serial was provided
    # serial will be sent back. If callback url or callback sms was provided
    # the corresponding data will be added, too

    #            ------------------------------------------------------- --
    #  fields   | .... | serial | NUL | cb url | NUL | cb sms | NUL | ... |
    #            ------------------------------------------------------- --
    #  size     |  42  |   ?    |  1  |   ?    |  1  |   ?    |  1  |  ?  |
    #            ------------------------------------------------------- --

    if flags & FLAG_PAIR_SERIAL:
        data += serial.encode("utf8") + b"\x00"
    if flags & FLAG_PAIR_CBURL:
        data += callback_url.encode("utf8") + b"\x00"
    if flags & FLAG_PAIR_CBSMS:
        data += callback_sms_number.encode("utf8") + b"\x00"

    # ---------------------------------------------------------------------- --

    # Other optional values: allowed pin length of otp (number of digits)
    # and custom hash algorithm

    #            ------------------------------------- --
    #  fields   | ... | otp pin length | hash_algorithm |
    #            ------------------------------------- --
    #  size     |  ?  |       1        |       1        |
    #            ------------------------------------- --

    if flags & FLAG_PAIR_DIGITS:
        if not (6 <= otp_pin_length <= 12):
            raise InvalidFunctionParameter(
                "otp_pin_length", "Pin length must be in the range 6..12"
            )
        data += struct.pack("<b", otp_pin_length)

    if flags & FLAG_PAIR_HMAC:
        try:
            HASH_ALGO = hash_algorithms[hash_algorithm]
        except KeyError:
            allowed_values = ", ".join(list(hash_algorithms.keys()))
            raise InvalidFunctionParameter(
                "hash_algorithm",
                "Unsupported hash algorithm %s, "
                "allowed values are %s" % (hash_algorithm, allowed_values),
            )
        data += struct.pack("<b", HASH_ALGO)

    # ---------------------------------------------------------------------- --

    # TODO missing token details for other protocols (hotp, hmac, etc)
    # * counter (u64le)
    # * tstart (u64le)
    # * tstep (u32le)

    if not (flags & FLAG_PAIR_PK):

        secret_key = get_secret_key(partition)
        server_sig = crypto_sign_detached(data, secret_key)
        data += server_sig

    protocol_id = config.get("mobile_app_protocol_id", "lseqr")
    return protocol_id + "://pair/" + encode_base64_urlsafe(data)