Ejemplo n.º 1
0
 def logoutChild(self, session_id):
     """expire a subsession"""
     if not self.logged_in:
         # XXX raise an error?
         raise koji.AuthError("Not logged in")
     update = """UPDATE sessions
     SET expired=TRUE,exclusive=NULL
     WHERE id = %(session_id)i AND master = %(master)i"""
     master = self.id
     c = context.cnx.cursor()
     c.execute(update, locals())
     context.cnx.commit()
Ejemplo n.º 2
0
    def sslLogin(self, proxyuser=None):
        if self.logged_in:
            raise koji.AuthError("Already logged in")

        if context.environ.get('REMOTE_USER'):
            username = context.environ.get('REMOTE_USER')
            client_dn = username
            authtype = koji.AUTHTYPE_GSSAPI
        else:
            if context.environ.get('SSL_CLIENT_VERIFY') != 'SUCCESS':
                raise koji.AuthError('could not verify client: %s' % context.environ.get('SSL_CLIENT_VERIFY'))

            name_dn_component = context.opts.get('DNUsernameComponent', 'CN')
            username = context.environ.get('SSL_CLIENT_S_DN_%s' % name_dn_component)
            if not username:
                raise koji.AuthError('unable to get user information (%s) from client certificate' % name_dn_component)
            client_dn = context.environ.get('SSL_CLIENT_S_DN')
            authtype = koji.AUTHTYPE_SSL

        if proxyuser:
            proxy_dns = [dn.strip() for dn in context.opts.get('ProxyDNs', '').split('|')]
            if client_dn in proxy_dns:
                # the SSL-authenticated user authorized to login other users
                username = proxyuser
            else:
                raise koji.AuthError('%s is not authorized to login other users' % client_dn)

        user_id = self.getUserId(username)
        if not user_id:
            if context.opts.get('LoginCreatesUser'):
                user_id = self.createUser(username)
            else:
                raise koji.AuthError('Unknown user: %s' % username)

        self.checkLoginAllowed(user_id)

        hostip = self.get_remote_ip()

        sinfo = self.createSession(user_id, hostip, authtype)
        return sinfo
Ejemplo n.º 3
0
    def createUserFromKerberos(self, krb_principal):
        """Create a new user, based on the Kerberos principal.  Their
        username will be everything before the "@" in the principal.
        Return the ID of the newly created user."""
        atidx = krb_principal.find('@')
        if atidx == -1:
            raise koji.AuthError('invalid Kerberos principal: %s' % krb_principal)
        user_name = krb_principal[:atidx]

        # check if user already exists
        c = context.cnx.cursor()
        q = """SELECT krb_principal FROM users
        WHERE name = %(user_name)s"""
        c.execute(q, locals())
        r = c.fetchone()
        if not r:
            return self.createUser(user_name, krb_principal=krb_principal)
        else:
            existing_user_krb = r[0]
            if existing_user_krb is not None:
                raise koji.AuthError('user %s already associated with other Kerberos principal: %s' % (user_name, existing_user_krb))
            return self.setKrbPrincipal(user_name, krb_principal)
Ejemplo n.º 4
0
 def setKrbPrincipal(self, name, krb_principal):
     usertype = koji.USERTYPES['NORMAL']
     status = koji.USER_STATUS['NORMAL']
     update = """UPDATE users SET krb_principal = %(krb_principal)s WHERE name = %(name)s AND usertype = %(usertype)i AND status = %(status)i RETURNING users.id"""
     cursor = context.cnx.cursor()
     cursor.execute(update, locals())
     r = cursor.fetchall()
     if len(r) != 1:
         context.cnx.rollback()
         raise koji.AuthError('could not automatically associate Kerberos Principal with existing user %s' % name)
     else:
         context.cnx.commit()
         return r[0][0]
Ejemplo n.º 5
0
 def logout(self):
     """expire a login session"""
     if not self.logged_in:
         #XXX raise an error?
         raise koji.AuthError("Not logged in")
     update = """UPDATE sessions
     SET expired=TRUE,exclusive=NULL
     WHERE id = %(id)i OR master = %(id)i"""
     #note we expire subsessions as well
     c = context.cnx.cursor()
     c.execute(update, {'id': self.id})
     context.cnx.commit()
     self.logged_in = False
Ejemplo n.º 6
0
 def setKrbPrincipal(self, name, krb_principal, krb_princ_check=True):
     if krb_princ_check:
         self.checkKrbPrincipal(krb_principal)
     select = """SELECT id FROM users WHERE %s"""
     if isinstance(name, six.integer_types):
         user_condition = 'id = %(name)i'
     else:
         user_condition = 'name = %(name)s'
     select = select % user_condition
     cursor = context.cnx.cursor()
     cursor.execute(select, locals())
     r = cursor.fetchone()
     if not r:
         context.cnx.rollback()
         raise koji.AuthError('No such user: %s' % name)
     else:
         user_id = r[0]
     insert = """INSERT INTO user_krb_principals (user_id, krb_principal)
                 VALUES (%(user_id)i, %(krb_principal)s)"""
     cursor.execute(insert, locals())
     context.cnx.commit()
     return user_id
Ejemplo n.º 7
0
    def __init__(self, args=None, hostip=None):
        self.logged_in = False
        self.id = None
        self.master = None
        self.key = None
        self.user_id = None
        self.authtype = None
        self.hostip = None
        self.user_data = {}
        self.message = ''
        self.exclusive = False
        self.lockerror = None
        self.callnum = None
        # we look up perms, groups, and host_id on demand, see __getattr__
        self._perms = None
        self._groups = None
        self._host_id = ''
        #get session data from request
        if args is None:
            environ = getattr(context, 'environ', {})
            args = environ.get('QUERY_STRING', '')
            if not args:
                self.message = 'no session args'
                return
            args = urlparse.parse_qs(args, strict_parsing=True)
        hostip = self.get_remote_ip(override=hostip)
        try:
            id = int(args['session-id'][0])
            key = args['session-key'][0]
        except KeyError as field:
            raise koji.AuthError('%s not specified in session args' % field)
        try:
            callnum = args['callnum'][0]
        except:
            callnum = None
        #lookup the session
        c = context.cnx.cursor()
        fields = {
            'authtype': 'authtype',
            'callnum': 'callnum',
            'exclusive': 'exclusive',
            'expired': 'expired',
            'master': 'master',
            'start_time': 'start_time',
            'update_time': 'update_time',
            'EXTRACT(EPOCH FROM start_time)': 'start_ts',
            'EXTRACT(EPOCH FROM update_time)': 'update_ts',
            'user_id': 'user_id',
            }
        # sort for stability (unittests)
        fields, aliases = list(zip(*list(sorted(fields.items(), key=lambda x: x[1]))))
        q = """
        SELECT %s FROM sessions
        WHERE id = %%(id)i
        AND key = %%(key)s
        AND hostip = %%(hostip)s
        FOR UPDATE
        """ % ",".join(fields)
        c.execute(q, locals())
        row = c.fetchone()
        if not row:
            raise koji.AuthError('Invalid session or bad credentials')
        session_data = dict(list(zip(aliases, row)))
        #check for expiration
        if session_data['expired']:
            raise koji.AuthExpired('session "%i" has expired' % id)
        #check for callnum sanity
        if callnum is not None:
            try:
                callnum = int(callnum)
            except (ValueError, TypeError):
                raise koji.AuthError("Invalid callnum: %r" % callnum)
            lastcall = session_data['callnum']
            if lastcall is not None:
                if lastcall > callnum:
                    raise koji.SequenceError("%d > %d (session %d)" \
                            % (lastcall, callnum, id))
                elif lastcall == callnum:
                    #Some explanation:
                    #This function is one of the few that performs its own commit.
                    #However, our storage of the current callnum is /after/ that
                    #commit. This means the the current callnum only gets committed if
                    #a commit happens afterward.
                    #We only schedule a commit for dml operations, so if we find the
                    #callnum in the db then a previous attempt succeeded but failed to
                    #return. Data was changed, so we cannot simply try the call again.
                    method = getattr(context, 'method', 'UNKNOWN')
                    if method not in RetryWhitelist:
                        raise koji.RetryError(
                            "unable to retry call %d (method %s) for session %d" \
                            % (callnum, method, id))

        # read user data
        #historical note:
        # we used to get a row lock here as an attempt to maintain sanity of exclusive
        # sessions, but it was an imperfect approach and the lock could cause some
        # performance issues.
        fields = ('name', 'status', 'usertype')
        q = """SELECT %s FROM users WHERE id=%%(user_id)s""" % ','.join(fields)
        c.execute(q, session_data)
        user_data = dict(list(zip(fields, c.fetchone())))

        if user_data['status'] != koji.USER_STATUS['NORMAL']:
            raise koji.AuthError('logins by %s are not allowed' % user_data['name'])
        #check for exclusive sessions
        if session_data['exclusive']:
            #we are the exclusive session for this user
            self.exclusive = True
        else:
            #see if an exclusive session exists
            q = """SELECT id FROM sessions WHERE user_id=%(user_id)s
            AND "exclusive" = TRUE AND expired = FALSE"""
            #should not return multiple rows (unique constraint)
            c.execute(q, session_data)
            row = c.fetchone()
            if row:
                (excl_id,) = row
                if excl_id == session_data['master']:
                    #(note excl_id cannot be None)
                    #our master session has the lock
                    self.exclusive = True
                else:
                    #a session unrelated to us has the lock
                    self.lockerror = "User locked by another session"
                    # we don't enforce here, but rely on the dispatcher to enforce
                    # if appropriate (otherwise it would be impossible to steal
                    # an exclusive session with the force option).

        # update timestamp
        q = """UPDATE sessions SET update_time=NOW() WHERE id = %(id)i"""
        c.execute(q, locals())
        #save update time
        context.cnx.commit()

        #update callnum (this is deliberately after the commit)
        #see earlier note near RetryError
        if callnum is not None:
            q = """UPDATE sessions SET callnum=%(callnum)i WHERE id = %(id)i"""
            c.execute(q, locals())

        # record the login data
        self.id = id
        self.key = key
        self.hostip = hostip
        self.callnum = callnum
        self.user_id = session_data['user_id']
        self.authtype = session_data['authtype']
        self.master = session_data['master']
        self.session_data = session_data
        self.user_data = user_data
        self.logged_in = True
Ejemplo n.º 8
0
    def krbLogin(self, krb_req, proxyuser=None):
        """Authenticate the user using the base64-encoded
        AP_REQ message in krb_req.  If proxyuser is not None,
        log in that user instead of the user associated with the
        Kerberos principal.  The principal must be an authorized
        "proxy_principal" in the server config."""
        if self.logged_in:
            raise koji.AuthError("Already logged in")

        if krbV is None:
            # python3 is not supported
            raise koji.AuthError("krbV module not installed")

        if not (context.opts.get('AuthPrincipal') and context.opts.get('AuthKeytab')):
            raise koji.AuthError('not configured for Kerberos authentication')

        ctx = krbV.default_context()
        srvprinc = krbV.Principal(name=context.opts.get('AuthPrincipal'), context=ctx)
        srvkt = krbV.Keytab(name=context.opts.get('AuthKeytab'), context=ctx)

        ac = krbV.AuthContext(context=ctx)
        ac.flags = krbV.KRB5_AUTH_CONTEXT_DO_SEQUENCE|krbV.KRB5_AUTH_CONTEXT_DO_TIME
        conninfo = self.getConnInfo()
        ac.addrs = conninfo

        # decode and read the authentication request
        req = base64.decodestring(krb_req)
        ac, opts, sprinc, ccreds = ctx.rd_req(req, server=srvprinc, keytab=srvkt,
                                              auth_context=ac,
                                              options=krbV.AP_OPTS_MUTUAL_REQUIRED)
        cprinc = ccreds[2]

        # Successfully authenticated via Kerberos, now log in
        if proxyuser:
            proxyprincs = [princ.strip() for princ in context.opts.get('ProxyPrincipals', '').split(',')]
            if cprinc.name in proxyprincs:
                login_principal = proxyuser
            else:
                raise koji.AuthError(
                      'Kerberos principal %s is not authorized to log in other users' % cprinc.name)
        else:
            login_principal = cprinc.name
        user_id = self.getUserIdFromKerberos(login_principal)
        if not user_id:
            user_id = self.getUserId(login_principal)
            if not user_id:
                # Only do autocreate if we also couldn't find by username AND the proxyuser
                # looks like a krb5 principal
                if context.opts.get('LoginCreatesUser') and '@' in login_principal:
                    user_id = self.createUserFromKerberos(login_principal)
                else:
                    raise koji.AuthError('Unknown Kerberos principal: %s' % login_principal)

        self.checkLoginAllowed(user_id)

        hostip = self.get_remote_ip()

        sinfo = self.createSession(user_id, hostip, koji.AUTHTYPE_KERB)

        # encode the reply
        rep = ctx.mk_rep(auth_context=ac)
        rep_enc = base64.encodestring(rep)

        # encrypt and encode the login info
        sinfo_priv = ac.mk_priv('%(session-id)s %(session-key)s' % sinfo)
        sinfo_enc = base64.encodestring(sinfo_priv)

        return (rep_enc, sinfo_enc, conninfo)
Ejemplo n.º 9
0
    def sslLogin(self, proxyuser=None):
        if self.logged_in:
            raise koji.AuthError("Already logged in")

        # we use REMOTE_USER to identify user
        if context.environ.get('REMOTE_USER'):
            # it is kerberos principal rather than user's name.
            username = context.environ.get('REMOTE_USER')
            client_dn = username
            authtype = koji.AUTHTYPE_GSSAPI
        else:
            if context.environ.get('SSL_CLIENT_VERIFY') != 'SUCCESS':
                raise koji.AuthError('could not verify client: %s' %
                                     context.environ.get('SSL_CLIENT_VERIFY'))

            name_dn_component = context.opts.get('DNUsernameComponent', 'CN')
            username = context.environ.get('SSL_CLIENT_S_DN_%s' % name_dn_component)
            if not username:
                raise koji.AuthError(
                    'unable to get user information (%s) from client certificate' %
                    name_dn_component)
            client_dn = context.environ.get('SSL_CLIENT_S_DN')
            authtype = koji.AUTHTYPE_SSL

        if proxyuser:
            if authtype == koji.AUTHTYPE_GSSAPI:
                delimiter = ','
                proxy_opt = 'ProxyPrincipals'
            else:
                delimiter = '|'
                proxy_opt = 'ProxyDNs'
            proxy_dns = [dn.strip() for dn in context.opts.get(proxy_opt, '').split(delimiter)]

            # backwards compatible for GSSAPI.
            # in old way, proxy user whitelist is ProxyDNs.
            # TODO: this should be removed in future release
            if authtype == koji.AUTHTYPE_GSSAPI and not context.opts.get(
                    'DisableGSSAPIProxyDNFallback', False):
                proxy_dns += [dn.strip() for dn in
                              context.opts.get('ProxyDNs', '').split('|')]

            if client_dn in proxy_dns:
                # the user authorized to login other users
                username = proxyuser
            else:
                raise koji.AuthError('%s is not authorized to login other users' % client_dn)

        if authtype == koji.AUTHTYPE_GSSAPI and '@' in username:
            user_id = self.getUserIdFromKerberos(username)
        else:
            user_id = self.getUserId(username)
        if not user_id:
            if context.opts.get('LoginCreatesUser'):
                if authtype == koji.AUTHTYPE_GSSAPI and '@' in username:
                    user_id = self.createUserFromKerberos(username)
                else:
                    user_id = self.createUser(username)
            else:
                raise koji.AuthError('Unknown user: %s' % username)

        self.checkLoginAllowed(user_id)

        hostip = self.get_remote_ip()

        sinfo = self.createSession(user_id, hostip, authtype)
        return sinfo
Ejemplo n.º 10
0
Archivo: auth.py Proyecto: tkdchen/koji
    def sslLogin(self, proxyuser=None, proxyauthtype=None):
        """Login into brew via SSL. proxyuser name can be specified and if it is
        allowed in the configuration file then connection is allowed to login as
        that user. By default we assume that proxyuser is coming via same
        authentication mechanism but proxyauthtype can be set to koji.AUTHTYPE_*
        value for different handling. Typical case is proxying kerberos user via
        web ui which itself is authenticated via SSL certificate. (See kojiweb
        for usage).

        proxyauthtype is working only if AllowProxyAuthType option is set to
        'On' in the hub.conf
        """
        if self.logged_in:
            raise koji.AuthError("Already logged in")

        # we use REMOTE_USER to identify user
        if context.environ.get('REMOTE_USER'):
            # it is kerberos principal rather than user's name.
            username = context.environ.get('REMOTE_USER')
            client_dn = username
            authtype = koji.AUTHTYPE_GSSAPI
        else:
            if context.environ.get('SSL_CLIENT_VERIFY') != 'SUCCESS':
                raise koji.AuthError('could not verify client: %s' %
                                     context.environ.get('SSL_CLIENT_VERIFY'))

            name_dn_component = context.opts.get('DNUsernameComponent', 'CN')
            username = context.environ.get('SSL_CLIENT_S_DN_%s' %
                                           name_dn_component)
            if not username:
                raise koji.AuthError(
                    'unable to get user information (%s) from client certificate'
                    % name_dn_component)
            client_dn = context.environ.get('SSL_CLIENT_S_DN')
            authtype = koji.AUTHTYPE_SSL

        if proxyuser:
            if authtype == koji.AUTHTYPE_GSSAPI:
                delimiter = ','
                proxy_opt = 'ProxyPrincipals'
            else:
                delimiter = '|'
                proxy_opt = 'ProxyDNs'
            proxy_dns = [
                dn.strip()
                for dn in context.opts.get(proxy_opt, '').split(delimiter)
            ]

            # backwards compatible for GSSAPI.
            # in old way, proxy user whitelist is ProxyDNs.
            # TODO: this should be removed in future release
            if authtype == koji.AUTHTYPE_GSSAPI and not context.opts.get(
                    'DisableGSSAPIProxyDNFallback', False):
                proxy_dns += [
                    dn.strip()
                    for dn in context.opts.get('ProxyDNs', '').split('|')
                ]

            if client_dn in proxy_dns:
                # the user authorized to login other users
                username = proxyuser
            else:
                raise koji.AuthError(
                    '%s is not authorized to login other users' % client_dn)

            # in this point we can continue with proxied user in same way as if it is not proxied
            if proxyauthtype is not None:
                if not context.opts['AllowProxyAuthType']:
                    raise koji.AuthError(
                        "Proxy must use same auth mechanism as hub (behaviour "
                        "can be overriden via AllowProxyAuthType hub option)")
                if proxyauthtype not in (koji.AUTHTYPE_GSSAPI,
                                         koji.AUTHTYPE_SSL):
                    raise koji.AuthError(
                        "Proxied authtype %s is not valid for sslLogin" %
                        proxyauthtype)
                authtype = proxyauthtype

        if authtype == koji.AUTHTYPE_GSSAPI and '@' in username:
            user_id = self.getUserIdFromKerberos(username)
        else:
            user_id = self.getUserId(username)
        if not user_id:
            if context.opts.get('LoginCreatesUser'):
                if authtype == koji.AUTHTYPE_GSSAPI and '@' in username:
                    user_id = self.createUserFromKerberos(username)
                else:
                    user_id = self.createUser(username)
            else:
                raise koji.AuthError('Unknown user: %s' % username)

        self.checkLoginAllowed(user_id)

        hostip = self.get_remote_ip()

        sinfo = self.createSession(user_id, hostip, authtype)
        return sinfo