Commits

"Sha...@ieee.org>"  committed f520614

Initial package of digestAuth

  • Participants

Comments (0)

Files changed (6)

+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#~ Project ignores
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+syntax: glob 
+
+syntax: regexp 
+
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#~ Generally useful ignore configurations
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+# zc.buildout preferences
+syntax: glob
+.buildout/*
+.mr.developer.cfg
+*.egg-info/*
+
+# auto-generated content
+syntax: glob 
+*.pyc
+*.pyo
+.noseids
+nosetests.xml
+
+# swap files of various origins
+syntax: glob 
+*.orig
+*~
+.*.swp
+.*.swo
+
+*.xcodeproj/*.mode1v3
+*.xcodeproj/*.pbxuser
+
+# Revision control dirs
+syntax: glob 
+\.hg/
+\.hg_archival.txt
+\.svn/
+CVS/
+
+# Various system generated files
+syntax: glob 
+Thumbs.db
+\.DS_Store
+
+
+syntax: regexp
+Icon\r
+
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#~ Concat Ignores
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+syntax: glob 
+
+Flask-DigestAuth
+================
+
+.. _Shane Holloway: http://bitbucket.org/shanewholloway
+.. _TechGame Networks: http://techgame.net
+.. _twitter (@shanewholloway): http://twitter.com/shanewholloway
+

File digestAuth/__init__.py

+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#~ Imports 
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+from .authdigest import *
+from .tools import *
+

File digestAuth/authdigest.py

+# -*- coding: utf-8 -*-
+"""
+    werkzeug.contrib.authdigest
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    The authdigest module contains classes to support
+    digest authentication compliant with RFC 2617.
+
+
+    Usage
+    =====
+
+    ::
+
+        from werkzeug.contrib.authdigest import RealmDigestDB
+
+        authDB = RealmDigestDB('test-realm')
+        authDB.add_user('admin', 'test')
+
+        def protectedResource(environ, start_reponse):
+            request = Request(environ)
+            if not authDB.isAuthenticated(request):
+                return authDB.challenge()
+
+            return get_protected_response(request)
+
+    :copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details.
+    :license: BSD, see LICENSE for more details.
+"""
+
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#~ Imports
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+import os
+import weakref
+import hashlib
+import werkzeug
+
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#~ Realm Digest Credentials Database
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+class RealmDigestDB(object):
+    """Database mapping user to hashed password.
+
+    Passwords are hashed using realm key, and specified
+    digest algorithm.
+
+    :param realm: string identifing the hashing realm
+    :param algorthm: string identifying hash algorithm to use,
+                     default is 'md5'
+    """
+
+    def __init__(self, realm, algorithm='md5'):
+        self.realm = realm
+        self.alg = self.newAlgorithm(algorithm)
+        self.db = self.newDB()
+
+    @property
+    def algorithm(self):
+        return self.alg.algorithm
+
+    def toDict(self):
+        r = {'cfg':{ 'algorithm': self.alg.algorithm,
+                'realm': self.realm},
+            'db': self.db, }
+        return r
+    def toJson(self, **kw):
+        import json
+        kw.setdefault('sort_keys', True)
+        kw.setdefault('indent', 2)
+        return json.dumps(self.toDict(), **kw)
+
+    def add_user(self, user, password):
+        r = self.alg.hashPassword(user, self.realm, password)
+        self.db[user] = r
+        return r
+
+    def __contains__(self, user):
+        return user in self.db
+    def get(self, user, default=None):
+        return self.db.get(user, default)
+    def __getitem__(self, user):
+        return self.db.get(user)
+    def __setitem__(self, user, password):
+        return self.add_user(user, password)
+    def __delitem__(self, user):
+        return self.db.pop(user, None)
+
+    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    def newDB(self):
+        return dict()
+    def newAlgorithm(self, algorithm):
+        return DigestAuthentication(algorithm)
+
+    def isAuthenticated(self, request, **kw):
+        authResult = AuthenticationResult(self)
+        request.authentication = authResult
+
+        authorization = request.authorization
+        if authorization is None:
+            return authResult.deny('initial', None)
+        authorization.result = authResult
+
+        hashPass = self[authorization.username]
+        if hashPass is None:
+            return authResult.deny('unknown_user')
+        elif not self.alg.verify(authorization, hashPass, **kw):
+            return authResult.deny('invalid_password')
+        else:
+            return authResult.approve('success')
+
+    challenge_class = werkzeug.Response
+    def challenge(self, response=None, status=401):
+        try:
+            authReq = response.www_authenticate
+        except AttributeError:
+            response = self.challenge_class(response, status)
+            authReq = response.www_authenticate
+        else:
+            if isinstance(status, (int, long)):
+                response.status_code = status
+            else: response.status = status
+
+        authReq.set_digest(self.realm, os.urandom(8).encode('hex'))
+        return response
+
+
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#~ Authentication Result
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+class AuthenticationResult(object):
+    """Authentication Result object
+
+    Created by RealmDigestDB.isAuthenticated to operate as a boolean result,
+    and storage of authentication information."""
+
+    authenticated = None
+    reason = None
+    status = 500
+
+    def __init__(self, authDB):
+        self.authDB = weakref.ref(authDB)
+
+    def __repr__(self):
+        return '<authenticated: %r reason: %r>' % (
+            self.authenticated, self.reason)
+    def __nonzero__(self):
+        return bool(self.authenticated)
+
+    def deny(self, reason, authenticated=False):
+        if bool(authenticated):
+            raise ValueError("Denied authenticated parameter must evaluate as False")
+        self.authenticated = authenticated
+        self.reason = reason
+        self.status = 401
+        return self
+
+    def approve(self, reason, authenticated=True):
+        if not bool(authenticated):
+            raise ValueError("Approved authenticated parameter must evaluate as True")
+        self.authenticated = authenticated
+        self.reason = reason
+        self.status = 200
+        return self
+
+    def challenge(self, response=None, force=False):
+        if force or not self:
+            return self.authDB().challenge(response, self.status)
+
+
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#~ Digest Authentication Algorithm
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+class DigestAuthentication(object):
+    """Digest Authentication implementation.
+
+    references:
+        "HTTP Authentication: Basic and Digest Access Authentication". RFC 2617.
+            http://tools.ietf.org/html/rfc2617
+
+        "Digest access authentication"
+            http://en.wikipedia.org/wiki/Digest_access_authentication
+    """
+
+    def __init__(self, algorithm='md5'):
+        self.algorithm = algorithm.lower()
+        self.H = self.hashAlgorithms[self.algorithm]
+
+    def verify(self, authorization, hashPass=None, **kw):
+        reqResponse = self.digest(authorization, hashPass, **kw)
+        if reqResponse:
+            return (authorization.response.lower() == reqResponse.lower())
+
+    def digest(self, authorization, hashPass=None, **kw):
+        if authorization is None:
+            return None
+
+        if hashPass is None:
+            hA1 = self._compute_hA1(authorization, kw['password'])
+        else: hA1 = hashPass
+
+        hA2 = self._compute_hA2(authorization, kw.pop('method', 'GET'))
+
+        if 'auth' in authorization.qop:
+            res = self._compute_qop_auth(authorization, hA1, hA2)
+        elif not authorization.qop:
+            res = self._compute_qop_empty(authorization, hA1, hA2)
+        else:
+            raise ValueError("Unsupported qop: %r" % (authorization.qop,))
+        return res
+
+    def hashPassword(self, username, realm, password):
+        return self.H(username, realm, password)
+
+    def _compute_hA1(self, auth, password=None):
+        return self.hashPassword(auth.username, auth.realm, password or auth.password)
+    def _compute_hA2(self, auth, method):
+        return self.H(method, auth.uri)
+    def _compute_qop_auth(self, auth, hA1, hA2):
+        return self.H(hA1, auth.nonce, auth.nc, auth.cnonce, auth.qop, hA2)
+    def _compute_qop_empty(self, auth, hA1, hA2):
+        return self.H(hA1, auth.nonce, hA2)
+
+    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    hashAlgorithms = {}
+
+    @classmethod
+    def addDigestHashAlg(klass, key, hashObj):
+        key = key.lower()
+        def H(*args):
+            x = ':'.join(map(str, args))
+            return hashObj(x).hexdigest()
+
+        H.__name__ = "H_"+key
+        klass.hashAlgorithms[key] = H
+        return H
+
+DigestAuthentication.addDigestHashAlg('md5', hashlib.md5)
+DigestAuthentication.addDigestHashAlg('sha', hashlib.sha1)
+

File digestAuth/tools.py

+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#~ Imports 
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+from functools import wraps
+
+import flask
+from . import authdigest
+
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#~ Definitions 
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+def requires_auth(authDb):
+    return authDb.requires_auth
+
+def requires_session_key(key='username', msg='Not authorized', status=401):
+    def decorSessionVar(f):
+        @wraps(f)
+        def checkSessionVar(*args, **kw):
+            if key not in flask.session:
+                return Response(msg, status)
+            else:
+                return f(*args, **kw)
+
+        return checkSessionVar
+
+    return decorSessionVar
+
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+from . import authdigest
+
+class FlaskRealmDigestDB(authdigest.RealmDigestDB):
+    def requires_auth(self, f):
+        @wraps(f)
+        def decorated(*args, **kwargs):
+            request = flask.request
+            if not self.isAuthenticated(request):
+                return self.challenge()
+
+            flask.session['username'] = request.authorization.username
+            return f(*args, **kwargs)
+
+        return decorated
+
+#!/usr/bin/env python
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#~ Imports 
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+from setuptools import setup
+
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#~ Definitions 
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+__version__ = '0.1.0'
+
+setup(
+    name='Flask-DigestAuth',
+    version=__version__,
+    description='Uses greenlets and the gevent library to implement basic support for the actor model.  Inspired by iolanguage.com',
+    long_description=open('README.rst', 'rb').read(),
+    author='Shane Holloway',
+    author_email='shane@techgame.net',
+    url=None,
+    packages=['digestAuth'],
+    install_requires=["flask", "werkzeug"],
+    classifiers=[
+        "License :: OSI Approved :: MIT License",
+        "Programming Language :: Python",
+        "Operating System :: MacOS :: MacOS X",
+        "Operating System :: POSIX",
+        "Operating System :: Microsoft :: Windows",
+        "Topic :: Internet",
+        "Topic :: Software Development :: Libraries :: Python Modules",
+        "Intended Audience :: Developers",
+        "Development Status :: 4 - Beta"])
+