Tres Seaver
2017-12-20 a83610e170325e157726a4649923dff8ae303d12
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
from zope.interface import Interface
 
 
class IAPIFactory(Interface):
    def __call__(environ):
        """ environ -> IRepozeWhoAPI
        """
 
 
class IAPI(Interface):
    """ Facade for stateful invocation of underlying plugins.
    """
    def authenticate():
        """ ->  {identity}
 
        o Return an authenticated identity mapping, extracted from the
        request environment.
 
        o If no identity can be authenticated, return None.
 
        o Identity will include at least a 'repoze.who.userid' key,
        as well as any keys added by metadata plugins.
        """
 
    def challenge(status='403 Forbidden', app_headers=()):
        """ -> wsgi application
        
        o Return a WSGI application which represents a "challenge"
        (request for credentials) in response to the current request.  
        """
 
    def remember(identity=None):
        """ -> [headers]
        
        O Return a sequence of response headers which suffice to remember
        the given identity.
 
        o If 'identity' is not passed, use the identity in the environment.
        """
 
    def forget(identity=None):
        """ -> [headers]
        
        O Return a sequence of response headers which suffice to destroy
        any credentials used to establish an identity.
 
        o If 'identity' is not passed, use the identity in the environment.
        """
 
    def login(credentials, identifier_name=None):
        """ -> (identity, headers)
        
        o This is an API for browser-based application login forms.
        
        o If 'identifier_name' is passed, use it to look up the identifier;
          othewise, use the first configured identifier.
 
        o Attempt to authenticate 'credentials' as though the identifier
          had extracted them.
          
        o On success, 'identity' will be authenticated mapping, and 'headers'
          will be "remember" headers.
 
        o On failure, 'identity' will be None, and response_headers will be
          "forget" headers.
        """
 
    def logout(identifier_name=None):
        """ -> (headers)
        
        o This is an API for browser-based application logout.
        
        o If 'identifier_name' is passed, use it to look up the identifier;
          othewise, use the first configured identifier.
 
        o Returned headers will be "forget" headers.
        """
 
 
class IPlugin(Interface):
    pass
 
 
class IRequestClassifier(IPlugin):
    """ On ingress: classify a request.
    """
    def __call__(environ):
        """ environ -> request classifier string
 
        This interface is responsible for returning a string
        value representing a request classification.
 
        o 'environ' is the WSGI environment.
        """
 
 
class IChallengeDecider(IPlugin):
    """ On egress: decide whether a challenge needs to be presented
    to the user.
    """
    def __call__(environ, status, headers):
        """ args -> True | False
 
        o 'environ' is the WSGI environment.
 
        o 'status' is the HTTP status as returned by the downstream
          WSGI application.
 
        o 'headers' are the headers returned by the downstream WSGI
          application.
 
        This interface is responsible for returning True if
        a challenge needs to be presented to the user, False otherwise.
        """
 
 
class IIdentifier(IPlugin):
 
    """
    On ingress: Extract credentials from the WSGI environment and
    turn them into an identity.
 
    On egress (remember): Conditionally set information in the response headers
    allowing the remote system to remember this identity.
 
    On egress (forget): Conditionally set information in the response
    headers allowing the remote system to forget this identity (during
    a challenge).
    """
 
    def identify(environ):
        """ On ingress:
 
        environ -> {   k1 : v1
                       ,   ...
                       , kN : vN
                       } | None
 
        o 'environ' is the WSGI environment.
 
        o If credentials are found, the returned identity mapping will
          contain an arbitrary set of key/value pairs.  If the
          identity is based on a login and password, the environment
          is recommended to contain at least 'login' and 'password'
          keys as this provides compatibility between the plugin and
          existing authenticator plugins.  If the identity can be
          'preauthenticated' (e.g. if the userid is embedded in the
          identity, such as when we're using ticket-based
          authentication), the plugin should set the userid in the
          special 'repoze.who.userid' key; no authenticators will be
          asked to authenticate the identity thereafer.
 
        o Return None to indicate that the plugin found no appropriate
          credentials.
 
        o Only IIdentifier plugins which match one of the the current
          request's classifications will be asked to perform
          identification.
 
        o An identifier plugin is permitted to add a key to the
          environment named 'repoze.who.application', which should be
          an arbitrary WSGI application.  If an identifier plugin does
          so, this application is used instead of the downstream
          application set up within the middleware.  This feature is
          useful for identifier plugins which need to perform
          redirection to obtain credentials.  If two identifier
          plugins add a 'repoze.who.application' WSGI application to
          the environment, the last one consulted will"win".
        """
 
    def remember(environ, identity):
        """ On egress (no challenge required):
 
        args -> [ (header-name, header-value), ...] | None
 
        Return a list of headers suitable for allowing the requesting
        system to remember the identification information (e.g. a
        Set-Cookie header).  Return None if no headers need to be set.
        These headers will be appended to any headers returned by the
        downstream application.
        """
 
    def forget(environ, identity):
        """ On egress (challenge required):
 
        args -> [ (header-name, header-value), ...] | None
 
        Return a list of headers suitable for allowing the requesting
        system to forget the identification information (e.g. a
        Set-Cookie header with an expires date in the past).  Return
        None if no headers need to be set.  These headers will be
        included in the response provided by the challenge app.
        """
 
 
class IAuthenticator(IPlugin):
 
    """ On ingress: validate the identity and return a user id or None.
    """
 
    def authenticate(environ, identity):
        """ identity -> 'userid' | None
 
        o 'environ' is the WSGI environment.
 
        o 'identity' will be a dictionary (with arbitrary keys and
          values).
 
        o The IAuthenticator should return a single user id (optimally
          a string) if the identity can be authenticated.  If the
          identify cannot be authenticated, the IAuthenticator should
          return None.
 
        Each instance of a registered IAuthenticator plugin that
        matches the request classifier will be called N times during a
        single request, where N is the number of identities found by
        any IIdentifierPlugin instances.
 
        An authenticator must not raise an exception if it is provided
        an identity dictionary that it does not understand (e.g. if it
        presumes that 'login' and 'password' are keys in the
        dictionary, it should check for the existence of these keys
        before attempting to do anything; if they don't exist, it
        should return None).
 
        An authenticator is permitted to add extra keys to the 'identity'
        dictionary (e.g., to save metadata from a database query, rather
        than requiring a separate query from an IMetadataProvider plugin).
        """
 
 
class IChallenger(IPlugin):
 
    """ On egress: Conditionally initiate a challenge to the user to
        provide credentials.
 
        Only challenge plugins which match one of the the current
        response's classifications will be asked to perform a
        challenge.
    """
 
    def challenge(environ, status, app_headers, forget_headers):
        """ args -> WSGI application or None
 
        o 'environ' is the WSGI environment.
 
        o 'status' is the status written into start_response by the
          downstream application.
 
        o 'app_headers' is the headers list written into start_response by the
          downstream application.
 
        o 'forget_headers' is a list of headers which must be passed
          back in the response in order to perform credentials reset
          (logout).  These come from the 'forget' method of
          IIdentifier plugin used to do the request's identification.
 
        Examine the values passed in and return a WSGI application
        (a callable which accepts environ and start_response as its
        two positional arguments, ala PEP 333) which causes a
        challenge to be performed.  Return None to forego performing a
        challenge.
        """
 
 
class IMetadataProvider(IPlugin):
    """On ingress: When an identity is authenticated, metadata
       providers may scribble on the identity dictionary arbitrarily.
       Return values from metadata providers are ignored.
    """
    
    def add_metadata(environ, identity):
        """
        Add metadata to the identity (which is a dictionary).  One
        value is always guaranteed to be in the dictionary when
        add_metadata is called: 'repoze.who.userid', representing the
        user id of the identity.  Availability and composition of
        other keys will depend on the identifier plugin which created
        the identity.
        """