Skip to content
This repository has been archived by the owner on Feb 17, 2023. It is now read-only.

Commit

Permalink
- Fix for issue 682: Quoting realm in WWW-Authenticate header properly
Browse files Browse the repository at this point in the history
  • Loading branch information
Christian Theune committed Aug 14, 2006
1 parent 6751fca commit e73a775
Show file tree
Hide file tree
Showing 5 changed files with 605 additions and 0 deletions.
40 changes: 40 additions & 0 deletions basicauthadapter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""HTTP Basic Authentication adapter
$Id$
"""
from zope.publisher.interfaces.http import IHTTPCredentials
from loginpassword import LoginPassword


class BasicAuthAdapter(LoginPassword):
"""Adapter for handling HTTP Basic Auth."""

__used_for__ = IHTTPCredentials

__request = None

def __init__(self, request):
self.__request = request
# TODO base64 decoding should be done here, not in request
lpw = request._authUserPW()
if lpw is None:
login, password = None, None
else:
login, password = lpw
LoginPassword.__init__(self, login, password)

def needLogin(self, realm):
self.__request.unauthorized('basic realm="%s"'% realm)
166 changes: 166 additions & 0 deletions browser/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Login and Logout screens
$Id$
"""
import urllib
from zope.interface import implements
from zope.i18n import translate
from zope import component
from zope.app.publisher.interfaces.http import ILogin
from zope.app.security.interfaces import IAuthentication
from zope.app.security.interfaces import IUnauthenticatedPrincipal
from zope.app.security.interfaces import ILogout, ILogoutSupported
from zope.app.pagetemplate import ViewPageTemplateFile
from zope.app.i18n import ZopeMessageFactory as _


search_label = _('search-button', 'Search')
search_title = _('Search String')

class AuthUtilitySearchView(object):

__used_for__ = IAuthentication

def __init__(self, context, request):
self.context = context
self.request = request

def render(self, name):
sourcename = 'principals.zcml'
html = []

# add sub title for source search field
html.append('<h4>%s</h4>' % sourcename)
# start row for search fields
html.append('<div class="row">')
html.append('<div class="label">')
html.append(translate(search_title, context=self.request))
html.append('</div>')
html.append('<div class="field">')
html.append('<input type="text" name="%s" />' %(name+'.searchstring'))
html.append('</div>')
html.append('</div>')

# add search button for search fields
html.append('<div class="row">')
html.append('<div class="field">')
html.append('<input type="submit" name="%s" value="%s" />'
% (name+'.search',
translate(search_label, context=self.request)))
html.append('</div>')

# end row
html.append('</div>')

return '\n'.join(html)

def results(self, name):
if not (name+'.search' in self.request):
return None
searchstring = self.request[name+'.searchstring']
return [principal.id
for principal in self.context.getPrincipals(searchstring)]


class HTTPAuthenticationLogin(object):

implements(ILogin)

confirmation = ViewPageTemplateFile('login.pt')

failed = ViewPageTemplateFile('login_failed.pt')

def login(self, nextURL=None):
# we don't want to keep challenging if we're authenticated
if IUnauthenticatedPrincipal.providedBy(self.request.principal):
component.getUtility(IAuthentication).unauthorized(
self.request.principal.id, self.request)
return self.failed()
else:
if nextURL is None:
return self.confirmation()
else:
self.request.response.redirect(nextURL)


class HTTPBasicAuthenticationLogin(HTTPAuthenticationLogin):
"""Issues a challenge to the browser to get basic auth credentials.
This view can be used as a fail safe login in the even the normal login
fails because of an improperly configured authentication utility.
The failsafeness of this view relies on the fact that the global principal
registry, which typically contains an adminitrator principal, uses basic
auth credentials to authenticate.
"""
def login(self, nextURL=None):
# we don't want to keep challenging if we're authenticated
if IUnauthenticatedPrincipal.providedBy(self.request.principal):
# hard-code basic auth challenge
self.request.unauthorized('basic realm="Zope"')
return self.failed()
else:
if nextURL is None:
return self.confirmation()
else:
self.request.response.redirect(nextURL)


class HTTPAuthenticationLogout(object):
"""Since HTTP Authentication really does not know about logout, we are
simply challenging the client again."""

implements(ILogout)

confirmation = ViewPageTemplateFile('logout.pt')

redirect = ViewPageTemplateFile('redirect.pt')

def __init__(self, context, request):
self.context = context
self.request = request

def logout(self, nextURL=None):
if not IUnauthenticatedPrincipal.providedBy(self.request.principal):
auth = component.getUtility(IAuthentication)
ILogout(auth).logout(self.request)
if nextURL:
return self.redirect()
if nextURL is None:
return self.confirmation()
else:
return self.request.response.redirect(nextURL)


class LoginLogout(object):

def __init__(self, context, request):
self.context = context
self.request = request

def __call__(self):
if IUnauthenticatedPrincipal.providedBy(self.request.principal):
return u'<a href="@@login.html?nextURL=%s">%s</a>' % (
urllib.quote(self.request.getURL()),
translate(_('[Login]'), context=self.request,
default='[Login]'))
elif ILogoutSupported(self.request, None) is not None:
return u'<a href="@@logout.html?nextURL=%s">%s</a>' % (
urllib.quote(self.request.getURL()),
translate(_('[Logout]'), context=self.request,
default='[Logout]'))
else:
return None
184 changes: 184 additions & 0 deletions principalregistry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Global Authentication Utility or Principal Registry
$Id$
"""
from zope.interface import implements

from zope.app.authentication.interfaces import IPasswordManager
from zope.app.security.interfaces import PrincipalLookupError
from zope.app import zapi
from zope.security.interfaces import IPrincipal, IGroupAwarePrincipal
from zope.app.security import interfaces
from zope.app.container.contained import Contained, contained


class DuplicateLogin(Exception): pass
class DuplicateId(Exception): pass

class PrincipalRegistry(object):

implements(interfaces.IAuthentication, interfaces.ILogout)

# Methods implementing IAuthentication

def authenticate(self, request):
a = interfaces.ILoginPassword(request, None)
if a is not None:
login = a.getLogin()
if login is not None:
p = self.__principalsByLogin.get(login, None)
if p is not None:
password = a.getPassword()
if p.validate(password):
return p
return None

__defaultid = None
__defaultObject = None

def defineDefaultPrincipal(self, id, title, description='',
principal=None):
if id in self.__principalsById:
raise DuplicateId(id)
self.__defaultid = id
if principal is None:
principal = UnauthenticatedPrincipal(id, title, description)
self.__defaultObject = contained(principal, self, id)
return principal

def unauthenticatedPrincipal(self):
return self.__defaultObject

def unauthorized(self, id, request):
if id is None or id is self.__defaultid:
a = interfaces.ILoginPassword(request)
a.needLogin(realm="Zope")

def getPrincipal(self, id):
r = self.__principalsById.get(id)
if r is None:
if id == self.__defaultid:
return self.__defaultObject
raise PrincipalLookupError(id)
return r

def getPrincipalByLogin(self, login):
return self.__principalsByLogin[login]

def getPrincipals(self, name):
name = name.lower()
return [p for p in self.__principalsById.itervalues()
if p.title.lower().startswith(name) or
p.getLogin().lower().startswith(name)]

def logout(self, request):
# not supporting basic auth logout -- no such thing
pass

# Management methods

def __init__(self):
self.__principalsById = {}
self.__principalsByLogin = {}

def definePrincipal(self, principal, title, description='',
login='', password='', passwordManagerName='Plain Text'):
id=principal
if login in self.__principalsByLogin:
raise DuplicateLogin(login)

if id in self.__principalsById or id == self.__defaultid:
raise DuplicateId(id)

p = Principal(id, title, description,
login, password, passwordManagerName)
p = contained(p, self, id)

self.__principalsByLogin[login] = p
self.__principalsById[id] = p

return p

def registerGroup(self, group):
id = group.id
if id in self.__principalsById or id == self.__defaultid:
raise DuplicateId(id)

self.__principalsById[group.id] = group

def _clear(self):
self.__init__()
self.__defaultid = None
self.__defaultObject = None

principalRegistry = PrincipalRegistry()

# Register our cleanup with Testing.CleanUp to make writing unit tests simpler.
from zope.testing.cleanup import addCleanUp
addCleanUp(principalRegistry._clear)
del addCleanUp

class PrincipalBase(Contained):

def __init__(self, id, title, description):
self.id = id
self.title = title
self.description = description
self.groups = []

class Group(PrincipalBase):

def getLogin(self):
return '' # to make registry search happy

class Principal(PrincipalBase):

implements(IGroupAwarePrincipal)

def __init__(self, id, title, description, login,
pw, pwManagerName="Plain Text"):
super(Principal, self).__init__(id, title, description)
self.__login = login
self.__pwManagerName = pwManagerName
self.__pw = pw

def __getPasswordManager(self):
return zapi.getUtility(IPasswordManager, self.__pwManagerName)

def getLogin(self):
return self.__login

def validate(self, pw):
pwManager = self.__getPasswordManager()
return pwManager.checkPassword(self.__pw, pw)


class UnauthenticatedPrincipal(PrincipalBase):

implements(interfaces.IUnauthenticatedPrincipal)

class UnauthenticatedGroup(Group):

implements(interfaces.IUnauthenticatedGroup)

class AuthenticatedGroup(Group):

implements(interfaces.IAuthenticatedGroup)

class EverybodyGroup(Group):

implements(interfaces.IEveryoneGroup)

Loading

0 comments on commit e73a775

Please sign in to comment.