1  import logging 
  2  from StringIO import StringIO 
  3  import sys 
  4   
  5  from repoze.who.interfaces import IIdentifier 
  6  from repoze.who.interfaces import IAuthenticator 
  7  from repoze.who.interfaces import IChallenger 
  8  from repoze.who.interfaces import IMetadataProvider 
  9   
 10  _STARTED = '-- repoze.who request started (%s) --' 
 11  _ENDED = '-- repoze.who request ended (%s) --' 
 12   
 14 -    def __init__(self, app, 
 15                   identifiers, 
 16                   authenticators, 
 17                   challengers, 
 18                   mdproviders, 
 19                   classifier, 
 20                   challenge_decider, 
 21                   log_stream = None, 
 22                   log_level = logging.INFO, 
 23                   remote_user_key = 'REMOTE_USER', 
 24                   ): 
  25          iregistry, nregistry = make_registries(identifiers, authenticators, 
 26                                                 challengers, mdproviders) 
 27          self.registry = iregistry 
 28          self.name_registry = nregistry 
 29          self.app = app 
 30          self.classifier = classifier 
 31          self.challenge_decider = challenge_decider 
 32          self.remote_user_key = remote_user_key 
 33          self.logger = None 
 34          if isinstance(log_stream, logging.Logger): 
 35              self.logger = log_stream 
 36          elif log_stream: 
 37              handler = logging.StreamHandler(log_stream) 
 38              fmt = '%(asctime)s %(message)s' 
 39              formatter = logging.Formatter(fmt) 
 40              handler.setFormatter(formatter) 
 41              self.logger = logging.Logger('repoze.who') 
 42              self.logger.addHandler(handler) 
 43              self.logger.setLevel(log_level) 
  44   
 45 -    def __call__(self, environ, start_response): 
  46          if self.remote_user_key in environ: 
 47               
 48               
 49              return self.app(environ, start_response) 
 50   
 51          path_info = environ.get('PATH_INFO', None) 
 52   
 53          environ['repoze.who.plugins'] = self.name_registry 
 54          environ['repoze.who.logger'] = self.logger 
 55          environ['repoze.who.application'] = self.app 
 56   
 57          logger = self.logger 
 58          logger and logger.info(_STARTED % path_info) 
 59          classification = self.classifier(environ) 
 60          logger and logger.info('request classification: %s' % classification) 
 61          userid = None 
 62          identity = None 
 63          identifier = None 
 64   
 65          ids = self.identify(environ, classification) 
 66               
 67           
 68          if ids: 
 69              auth_ids = self.authenticate(environ, classification, ids) 
 70   
 71               
 72               
 73               
 74               
 75               
 76               
 77   
 78              if auth_ids: 
 79                  auth_ids.sort() 
 80                  best = auth_ids[0] 
 81                  rank, authenticator, identifier, identity, userid = best 
 82                  identity = Identity(identity)  
 83   
 84                   
 85                  self.add_metadata(environ, classification, identity) 
 86   
 87                   
 88                   
 89                   
 90                   
 91                  environ['repoze.who.identity'] = identity 
 92                   
 93                  environ[self.remote_user_key] = userid 
 94   
 95          else: 
 96              logger and logger.info('no identities found, not authenticating') 
 97   
 98           
 99           
100           
101          app = environ.pop('repoze.who.application') 
102          if  app is not self.app: 
103              logger and logger.info( 
104                  'static downstream application replaced with %s' % app) 
105   
106          wrapper = StartResponseWrapper(start_response) 
107          app_iter = app(environ, wrapper.wrap_start_response) 
108   
109           
110           
111           
112           
113           
114          if not wrapper.called: 
115              app_iter = wrap_generator(app_iter) 
116   
117          if self.challenge_decider(environ, wrapper.status, wrapper.headers): 
118              logger and logger.info('challenge required') 
119   
120              challenge_app = self.challenge( 
121                  environ, 
122                  classification, 
123                  wrapper.status, 
124                  wrapper.headers, 
125                  identifier, 
126                  identity 
127                  ) 
128              if challenge_app is not None: 
129                  logger and logger.info('executing challenge app') 
130                  if app_iter: 
131                      list(app_iter)  
132                   
133                  app_iter = challenge_app(environ, start_response) 
134              else: 
135                  logger and logger.info('configuration error: no challengers') 
136                  raise RuntimeError('no challengers found') 
137          else: 
138              logger and logger.info('no challenge required') 
139              remember_headers = [] 
140              if identifier: 
141                  remember_headers = identifier.remember(environ, identity) 
142                  if remember_headers: 
143                      logger and logger.info('remembering via headers from %s: %s' 
144                                             % (identifier, remember_headers)) 
145              wrapper.finish_response(remember_headers) 
146   
147          logger and logger.info(_ENDED % path_info) 
148          return app_iter 
 149   
150 -    def identify(self, environ, classification): 
 151          logger = self.logger 
152          candidates = self.registry.get(IIdentifier, ()) 
153          logger and self.logger.info('identifier plugins registered %s' % 
154                                      (candidates,)) 
155          plugins = match_classification(IIdentifier, candidates, classification) 
156          logger and self.logger.info( 
157              'identifier plugins matched for ' 
158              'classification "%s": %s' % (classification, plugins)) 
159   
160          results = [] 
161          for plugin in plugins: 
162              identity = plugin.identify(environ) 
163              if identity is not None: 
164                  logger and logger.debug( 
165                      'identity returned from %s: %s' % (plugin, identity)) 
166                  results.append((plugin, identity)) 
167              else: 
168                  logger and logger.debug( 
169                      'no identity returned from %s (%s)' % (plugin, identity)) 
170   
171          logger and logger.debug('identities found: %s' % (results,)) 
172          return results 
 173   
180   
181 -    def authenticate(self, environ, classification, identities): 
 182          logger = self.logger 
183          candidates = self.registry.get(IAuthenticator, []) 
184          logger and self.logger.info('authenticator plugins registered %s' % 
185                                      candidates) 
186          plugins = match_classification(IAuthenticator, candidates, 
187                                         classification) 
188          logger and self.logger.info( 
189              'authenticator plugins matched for ' 
190              'classification "%s": %s' % (classification, plugins)) 
191   
192           
193          identities, results, id_rank_start =self._filter_preauthenticated( 
194              identities) 
195   
196          auth_rank = 0 
197   
198          for plugin in plugins: 
199              identifier_rank = id_rank_start 
200              for identifier, identity in identities: 
201                  userid = plugin.authenticate(environ, identity) 
202                  if userid is not None: 
203                      logger and logger.debug( 
204                          'userid returned from %s: "%s"' % (plugin, userid)) 
205   
206                       
207                      identity['repoze.who.userid'] = userid 
208                      rank = (auth_rank, identifier_rank) 
209                      results.append( 
210                          (rank, plugin, identifier, identity, userid) 
211                          ) 
212                  else: 
213                      logger and logger.debug( 
214                          'no userid returned from %s: (%s)' % ( 
215                          plugin, userid)) 
216                  identifier_rank += 1 
217              auth_rank += 1 
218   
219          logger and logger.debug('identities authenticated: %s' % (results,)) 
220          return results 
 221   
223          logger = self.logger 
224          results = [] 
225          new_identities = identities[:] 
226   
227          identifier_rank = 0 
228          for thing in identities: 
229              identifier, identity = thing 
230              userid = identity.get('repoze.who.userid') 
231              if userid is not None: 
232                   
233                   
234                  logger and logger.info( 
235                    'userid preauthenticated by %s: "%s" ' 
236                    '(repoze.who.userid set)' % (identifier, userid) 
237                    ) 
238                  rank = (0, identifier_rank) 
239                  results.append( 
240                      (rank, None, identifier, identity, userid) 
241                      ) 
242                  identifier_rank += 1 
243                  new_identities.remove(thing) 
244          return new_identities, results, identifier_rank 
 245   
246 -    def challenge(self, environ, classification, status, app_headers, 
247                    identifier, identity): 
 248           
249          logger = self.logger 
250   
251          forget_headers = [] 
252   
253          if identifier: 
254              forget_headers = identifier.forget(environ, identity) 
255              if forget_headers is None: 
256                  forget_headers = [] 
257              else: 
258                  logger and logger.info('forgetting via headers from %s: %s' 
259                                         % (identifier, forget_headers)) 
260   
261          candidates = self.registry.get(IChallenger, ()) 
262          logger and logger.info('challengers registered: %s' % candidates) 
263          plugins = match_classification(IChallenger, 
264                                         candidates, classification) 
265          logger and logger.info('challengers matched for ' 
266                                 'classification "%s": %s' % (classification, 
267                                                              plugins)) 
268          for plugin in plugins: 
269              app = plugin.challenge(environ, status, app_headers, 
270                                     forget_headers) 
271              if app is not None: 
272                   
273                  logger and logger.info( 
274                      'challenger plugin %s "challenge" returned an app' % ( 
275                      plugin)) 
276                  return app 
277   
278           
279          logger and logger.info('no challenge app returned') 
280          return None 
  281   
283      """\ 
284      This function returns a generator that behaves exactly the same as the 
285      original.  It's only difference is it pulls the first iteration off and 
286      caches it to trigger any immediate side effects (in a WSGI world, this 
287      ensures start_response is called). 
288      """ 
289       
290       
291      for iter in result: 
292          first = iter 
293          break 
294   
295       
296       
297      def wrapper(): 
298          yield first 
299          for iter in result: 
300               
301              yield iter 
 302      return wrapper() 
303   
305      result = [] 
306      for plugin in plugins: 
307           
308          plugin_classifications = getattr(plugin, 'classifications', {}) 
309          iface_classifications = plugin_classifications.get(iface) 
310          if not iface_classifications:  
311              result.append(plugin) 
312              continue 
313          if classification in iface_classifications: 
314              result.append(plugin) 
315   
316      return result 
 317   
320          self.start_response = start_response 
321          self.status = None 
322          self.headers = [] 
323          self.exc_info = None 
324          self.buffer = StringIO() 
325           
326           
327           
328          self.called = False 
 329   
331          self.headers = headers 
332          self.status = status 
333          self.exc_info = exc_info 
334           
335          self.called = True 
336          return self.buffer.write 
 337   
339          if not extra_headers: 
340              extra_headers = [] 
341          headers = self.headers + extra_headers 
342          write = self.start_response(self.status, headers, self.exc_info) 
343          if write: 
344              self.buffer.seek(0) 
345              value = self.buffer.getvalue() 
346              if value: 
347                  write(value) 
348              if hasattr(write, 'close'): 
349                  write.close() 
  350   
352      """ Functionally equivalent to 
353   
354      [plugin:form] 
355      use = repoze.who.plugins.form.FormPlugin 
356      rememberer_name = cookie 
357      login_form_qs=__do_login 
358   
359      [plugin:cookie] 
360      use = repoze.who.plugins.cookie:InsecureCookiePlugin 
361      cookie_name = oatmeal 
362   
363      [plugin:basicauth] 
364      use = repoze.who.plugins.basicauth.BasicAuthPlugin 
365      realm = repoze.who 
366   
367      [plugin:htpasswd] 
368      use = repoze.who.plugins.htpasswd.HTPasswdPlugin 
369      filename = <...> 
370      check_fn = repoze.who.plugins.htpasswd:crypt_check 
371   
372      [general] 
373      request_classifier = repoze.who.classifiers:default_request_classifier 
374      challenge_decider = repoze.who.classifiers:default_challenge_decider 
375   
376      [identifiers] 
377      plugins = form:browser cookie basicauth 
378   
379      [authenticators] 
380      plugins = htpasswd 
381   
382      [challengers] 
383      plugins = form:browser basicauth 
384      """ 
385       
386      from repoze.who.plugins.basicauth import BasicAuthPlugin 
387      from repoze.who.plugins.auth_tkt import AuthTktCookiePlugin 
388      from repoze.who.plugins.cookie import InsecureCookiePlugin 
389      from repoze.who.plugins.form import FormPlugin 
390      from repoze.who.plugins.htpasswd import HTPasswdPlugin 
391      io = StringIO() 
392      salt = 'aa' 
393      for name, password in [ ('admin', 'admin'), ('chris', 'chris') ]: 
394          io.write('%s:%s\n' % (name, password)) 
395      io.seek(0) 
396      def cleartext_check(password, hashed): 
397          return password == hashed  
 398      htpasswd = HTPasswdPlugin(io, cleartext_check) 
399      basicauth = BasicAuthPlugin('repoze.who') 
400      auth_tkt = AuthTktCookiePlugin('secret', 'auth_tkt') 
401      cookie = InsecureCookiePlugin('oatmeal') 
402      form = FormPlugin('__do_login', rememberer_name='auth_tkt') 
403      form.classifications = { IIdentifier:['browser'], 
404                               IChallenger:['browser'] }  
405      identifiers = [('form', form),('auth_tkt',auth_tkt),('basicauth',basicauth)] 
406      authenticators = [('htpasswd', htpasswd)] 
407      challengers = [('form',form), ('basicauth',basicauth)] 
408      mdproviders = [] 
409      from repoze.who.classifiers import default_request_classifier 
410      from repoze.who.classifiers import default_challenge_decider 
411      log_stream = None 
412      import os 
413      if os.environ.get('WHO_LOG'): 
414          log_stream = sys.stdout 
415      middleware = PluggableAuthenticationMiddleware( 
416          app, 
417          identifiers, 
418          authenticators, 
419          challengers, 
420          mdproviders, 
421          default_request_classifier, 
422          default_challenge_decider, 
423          log_stream = log_stream, 
424          log_level = logging.DEBUG 
425          ) 
426      return middleware 
427   
429      from zope.interface.verify import verifyObject 
430      verifyObject(iface, plugin, tentative=True) 
 431       
432 -def make_registries(identifiers, authenticators, challengers, mdproviders): 
 433      from zope.interface.verify import BrokenImplementation 
434      interface_registry = {} 
435      name_registry = {} 
436   
437      for supplied, iface in [ (identifiers, IIdentifier), 
438                               (authenticators, IAuthenticator), 
439                               (challengers, IChallenger), 
440                               (mdproviders, IMetadataProvider)]: 
441   
442          for name, value in supplied: 
443              try: 
444                  verify(value, iface) 
445              except BrokenImplementation, why: 
446                  why = str(why) 
447                  raise ValueError(str(name) + ': ' + why) 
448              L = interface_registry.setdefault(iface, []) 
449              L.append(value) 
450              name_registry[name] = value 
451   
452      return interface_registry, name_registry 
 453   
455      """ dict subclass that prevents its members from being rendered 
456      during print """ 
458          return '<repoze.who identity (hidden, dict-like) at %s>' % id(self) 
 459      __str__ = __repr__ 
 460