[PATCH] - Added PAM support for hub - Added BasicAuth support for web

Christos Triantafyllidis christos.triantafyllidis at gmail.com
Wed Aug 5 15:14:40 UTC 2015


---
 hub/hub.conf                  |  4 +++-
 hub/kojixmlrpc.py             |  2 ++
 koji.spec                     |  1 +
 koji/auth.py                  | 33 +++++++++++++++++++++++++--------
 koji/server.py                |  2 ++
 www/conf/kojiweb.conf         |  5 +++++
 www/conf/web.conf             |  3 +++
 www/kojiweb/index.py          | 18 +++++++++++++++++-
 www/kojiweb/wsgi_publisher.py |  9 +++++++--
 9 files changed, 65 insertions(+), 12 deletions(-)

diff --git a/hub/hub.conf b/hub/hub.conf
index f1e40c1..e4fd77a 100644
--- a/hub/hub.conf
+++ b/hub/hub.conf
@@ -36,7 +36,9 @@ KojiDir = /mnt/koji
 
 ## end SSL client certificate auth configuration
 
-
+##  PAM auth configuration ##
+# PAMService = koji
+## end PAM auth configuration ##
 
 ##  Other options  ##
 LoginCreatesUser = On
diff --git a/hub/kojixmlrpc.py b/hub/kojixmlrpc.py
index efb99a6..2ecc4d1 100644
--- a/hub/kojixmlrpc.py
+++ b/hub/kojixmlrpc.py
@@ -426,6 +426,8 @@ def load_config(environ):
         ['DNUsernameComponent', 'string', 'CN'],
         ['ProxyDNs', 'string', ''],
 
+        ['PAMService', 'string', None],
+
         ['LoginCreatesUser', 'boolean', True],
         ['KojiWebURL', 'string', 'http://localhost.localdomain/koji'],
         ['EmailDomain', 'string', None],
diff --git a/koji.spec b/koji.spec
index 8a65b6f..73c4132 100644
--- a/koji.spec
+++ b/koji.spec
@@ -47,6 +47,7 @@ License: LGPLv2 and GPLv2
 Requires: httpd
 Requires: mod_wsgi
 Requires: postgresql-python
+Requires: python-pam
 Requires: %{name} = %{version}-%{release}
 
 %description hub
diff --git a/koji/auth.py b/koji/auth.py
index d419d77..f7971ed 100644
--- a/koji/auth.py
+++ b/koji/auth.py
@@ -27,6 +27,7 @@ import krbV
 import koji
 import cgi      #for parse_qs
 from context import context
+import pam
 
 # 1 - load session if provided
 #       - check uri for session id
@@ -267,14 +268,30 @@ class Session(object):
                 hostip = socket.gethostbyname(socket.gethostname())
 
         # check passwd
-        c = context.cnx.cursor()
-        q = """SELECT id FROM users
-        WHERE name = %(user)s AND password = %(password)s"""
-        c.execute(q,locals())
-        r = c.fetchone()
-        if not r:
-            raise koji.AuthError, 'invalid username or password'
-        user_id = r[0]
+        if context.opts.get('PAMService'):
+            if not pam.authenticate(user,password,context.opts.get('PAMService'):
+                raise koji.AuthError, 'invalid username or password'
+            cursor = context.cnx.cursor()
+            query = """SELECT id FROM users
+            WHERE name = %(user)s"""
+            cursor.execute(query, locals())
+            result = cursor.fetchone()
+            if result:
+                user_id = result[0]
+            else:
+                if context.opts.get('LoginCreatesUser'):
+                    user_id = self.createUser(user)
+                else:
+                    raise koji.AuthError, 'Unknown user: %s' % user
+        else:
+            c = context.cnx.cursor()
+            q = """SELECT id FROM users
+            WHERE name = %(user)s AND password = %(password)s"""
+            c.execute(q,locals())
+            r = c.fetchone()
+            if not r:
+                raise koji.AuthError, 'invalid username or password'
+            user_id = r[0]
 
         self.checkLoginAllowed(user_id)
 
diff --git a/koji/server.py b/koji/server.py
index 52f13f5..5fbf832 100644
--- a/koji/server.py
+++ b/koji/server.py
@@ -36,6 +36,8 @@ class ServerError(Exception):
 class ServerRedirect(ServerError):
     """Used to handle redirects"""
 
+class NotAuthorized(ServerError):
+    """Used to handle unauthorized"""
 
 class WSGIWrapper(object):
     """A very thin wsgi compat layer for mod_python
diff --git a/www/conf/kojiweb.conf b/www/conf/kojiweb.conf
index 3173ba2..b1d93ca 100644
--- a/www/conf/kojiweb.conf
+++ b/www/conf/kojiweb.conf
@@ -49,6 +49,11 @@ Alias /koji "/usr/share/koji-web/scripts/wsgi_publisher.py"
 #     SSLOptions +StdEnvVars
 # </Location>
 
+# uncomment this to enable authentication via BasicAuth
+# <Location /koji/login>
+#     WSGIPassAuthorization On
+# </Location>
+
 Alias /koji-static/ "/usr/share/koji-web/static/"
 
 <Directory "/usr/share/koji-web/static/">
diff --git a/www/conf/web.conf b/www/conf/web.conf
index 38f0b61..faad004 100644
--- a/www/conf/web.conf
+++ b/www/conf/web.conf
@@ -18,6 +18,9 @@ KojiFilesURL = http://server.example.com/kojifiles
 # ClientCA = /etc/kojiweb/clientca.crt
 # KojiHubCA = /etc/kojiweb/kojihubca.crt
 
+# BasicAuth authentication options
+# BasicAuthRealm = Koji
+
 LoginTimeout = 72
 
 # This must be changed and uncommented before deployment
diff --git a/www/kojiweb/index.py b/www/kojiweb/index.py
index 4be6131..656f1ff 100644
--- a/www/kojiweb/index.py
+++ b/www/kojiweb/index.py
@@ -33,7 +33,7 @@ import logging
 import time
 import koji
 import kojiweb.util
-from koji.server import ServerRedirect
+from koji.server import ServerRedirect, NotAuthorized
 from kojiweb.util import _initValues
 from kojiweb.util import _genHTML
 from kojiweb.util import _getValidTokens
@@ -253,6 +253,22 @@ def login(environ, page=None):
 
         username = principal
         authlogger.info('Successful Kerberos authentication by %s', username)
+    elif options['BasicAuthRealm']:
+        if environ['wsgi.url_scheme'] != 'https':
+            dest = 'login'
+            if page:
+                dest = dest + '?page=' + page
+            _redirectBack(environ, dest, forceSSL=True)
+            return
+
+        http_authorization = environ.get('HTTP_AUTHORIZATION')
+        if not http_authorization:
+            raise NotAuthorized
+        session.opts['user'], session.opts['password'] = http_authorization.split(' ')[1].decode('base64').split(':')
+        if not session.login():
+            raise koji.AuthError, 'could not login %s using those credentials' % http_username
+        username = session.opts['user']
+        authlogger.info('Successful BasicAuth authentication by %s', username)
     else:
         raise koji.AuthError, 'KojiWeb is incorrectly configured for authentication, contact the system administrator'
 
diff --git a/www/kojiweb/wsgi_publisher.py b/www/kojiweb/wsgi_publisher.py
index e790815..7f167c1 100644
--- a/www/kojiweb/wsgi_publisher.py
+++ b/www/kojiweb/wsgi_publisher.py
@@ -30,7 +30,7 @@ import sys
 import traceback
 
 from ConfigParser import RawConfigParser
-from koji.server import WSGIWrapper, ServerError, ServerRedirect
+from koji.server import WSGIWrapper, ServerError, ServerRedirect, NotAuthorized
 from koji.util import dslice
 
 
@@ -80,6 +80,8 @@ class Dispatcher(object):
         ['ClientCA', 'string', '/etc/kojiweb/clientca.crt'],
         ['KojiHubCA', 'string', '/etc/kojiweb/kojihubca.crt'],
 
+        ['BasicAuthRealm', 'string', None],
+
         ['PythonDebug', 'boolean', False],
 
         ['LoginTimeout', 'integer', 72],
@@ -141,7 +143,6 @@ class Dispatcher(object):
             config = None
         else:
             raise koji.GenericError, "Configuration missing"
-
         opts = {}
         for name, dtype, default in self.cfgmap:
             if config:
@@ -395,6 +396,10 @@ class Dispatcher(object):
             result, headers = self.error_page(environ, message=msg, err=False)
             start_response(status, headers)
             return result
+        except NotAuthorized:
+            status = "401 Not Authorized"
+            start_response(status, [('WWW-Authenticate', 'Basic realm="%s"' % self.options['BasicAuthRealm'])])
+            return '401 Not Authorized'
         except Exception:
             tb_str = ''.join(traceback.format_exception(*sys.exc_info()))
             self.logger.error(tb_str)
-- 
2.4.3



More information about the buildsys mailing list