1  """An implementation of the OpenID Provider Authentication Policy 
  2  Extension 1.0 
  3   
  4  @see: http://openid.net/developers/specs/ 
  5   
  6  @since: 2.1.0 
  7  """ 
  8   
  9  __all__ = [ 
 10      'Request', 
 11      'Response', 
 12      'ns_uri', 
 13      'AUTH_PHISHING_RESISTANT', 
 14      'AUTH_MULTI_FACTOR', 
 15      'AUTH_MULTI_FACTOR_PHYSICAL', 
 16      ] 
 17   
 18  from openid.extension import Extension 
 19  import re 
 20   
 21  ns_uri = "http://specs.openid.net/extensions/pape/1.0" 
 22   
 23  AUTH_MULTI_FACTOR_PHYSICAL = \ 
 24      'http://schemas.openid.net/pape/policies/2007/06/multi-factor-physical' 
 25  AUTH_MULTI_FACTOR = \ 
 26      'http://schemas.openid.net/pape/policies/2007/06/multi-factor' 
 27  AUTH_PHISHING_RESISTANT = \ 
 28      'http://schemas.openid.net/pape/policies/2007/06/phishing-resistant' 
 29   
 30  TIME_VALIDATOR = re.compile('^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\dZ$') 
 31   
 33      """A Provider Authentication Policy request, sent from a relying 
 34      party to a provider 
 35   
 36      @ivar preferred_auth_policies: The authentication policies that 
 37          the relying party prefers 
 38      @type preferred_auth_policies: [str] 
 39   
 40      @ivar max_auth_age: The maximum time, in seconds, that the relying 
 41          party wants to allow to have elapsed before the user must 
 42          re-authenticate 
 43      @type max_auth_age: int or NoneType 
 44      """ 
 45   
 46      ns_alias = 'pape' 
 47   
 48 -    def __init__(self, preferred_auth_policies=None, max_auth_age=None): 
  49          super(Request, self).__init__() 
 50          if not preferred_auth_policies: 
 51              preferred_auth_policies = [] 
 52   
 53          self.preferred_auth_policies = preferred_auth_policies 
 54          self.max_auth_age = max_auth_age 
  55   
 57          return bool(self.preferred_auth_policies or 
 58                      self.max_auth_age is not None) 
  59   
 61          """Add an acceptable authentication policy URI to this request 
 62   
 63          This method is intended to be used by the relying party to add 
 64          acceptable authentication types to the request. 
 65   
 66          @param policy_uri: The identifier for the preferred type of 
 67              authentication. 
 68          @see: http://openid.net/specs/openid-provider-authentication-policy-extension-1_0-01.html#auth_policies 
 69          """ 
 70          if policy_uri not in self.preferred_auth_policies: 
 71              self.preferred_auth_policies.append(policy_uri) 
  72   
 74          """@see: C{L{Extension.getExtensionArgs}} 
 75          """ 
 76          ns_args = { 
 77              'preferred_auth_policies':' '.join(self.preferred_auth_policies) 
 78              } 
 79   
 80          if self.max_auth_age is not None: 
 81              ns_args['max_auth_age'] = str(self.max_auth_age) 
 82   
 83          return ns_args 
  84   
 86          """Instantiate a Request object from the arguments in a 
 87          C{checkid_*} OpenID message 
 88          """ 
 89          self = cls() 
 90          args = request.message.getArgs(self.ns_uri) 
 91   
 92          if args == {}: 
 93              return None 
 94   
 95          self.parseExtensionArgs(args) 
 96          return self 
  97   
 98      fromOpenIDRequest = classmethod(fromOpenIDRequest) 
 99   
101          """Set the state of this request to be that expressed in these 
102          PAPE arguments 
103   
104          @param args: The PAPE arguments without a namespace 
105   
106          @rtype: None 
107   
108          @raises ValueError: When the max_auth_age is not parseable as 
109              an integer 
110          """ 
111   
112           
113          self.preferred_auth_policies = [] 
114   
115          policies_str = args.get('preferred_auth_policies') 
116          if policies_str: 
117              for uri in policies_str.split(' '): 
118                  if uri not in self.preferred_auth_policies: 
119                      self.preferred_auth_policies.append(uri) 
120   
121           
122          max_auth_age_str = args.get('max_auth_age') 
123          self.max_auth_age = None 
124   
125          if max_auth_age_str: 
126              try: 
127                  self.max_auth_age = int(max_auth_age_str) 
128              except ValueError: 
129                  pass 
 130   
132          """Given a list of authentication policy URIs that a provider 
133          supports, this method returns the subsequence of those types 
134          that are preferred by the relying party. 
135   
136          @param supported_types: A sequence of authentication policy 
137              type URIs that are supported by a provider 
138   
139          @returns: The sub-sequence of the supported types that are 
140              preferred by the relying party. This list will be ordered 
141              in the order that the types appear in the supported_types 
142              sequence, and may be empty if the provider does not prefer 
143              any of the supported authentication types. 
144   
145          @returntype: [str] 
146          """ 
147          return filter(self.preferred_auth_policies.__contains__, 
148                        supported_types) 
  149   
150  Request.ns_uri = ns_uri 
151   
152   
154      """A Provider Authentication Policy response, sent from a provider 
155      to a relying party 
156      """ 
157   
158      ns_alias = 'pape' 
159   
160 -    def __init__(self, auth_policies=None, auth_time=None, 
161                   nist_auth_level=None): 
 170   
172          """Add a authentication policy to this response 
173   
174          This method is intended to be used by the provider to add a 
175          policy that the provider conformed to when authenticating the user. 
176   
177          @param policy_uri: The identifier for the preferred type of 
178              authentication. 
179          @see: http://openid.net/specs/openid-provider-authentication-policy-extension-1_0-01.html#auth_policies 
180          """ 
181          if policy_uri not in self.auth_policies: 
182              self.auth_policies.append(policy_uri) 
 183   
185          """Create a C{L{Response}} object from a successful OpenID 
186          library response 
187          (C{L{openid.consumer.consumer.SuccessResponse}}) response 
188          message 
189   
190          @param success_response: A SuccessResponse from consumer.complete() 
191          @type success_response: C{L{openid.consumer.consumer.SuccessResponse}} 
192   
193          @rtype: Response or None 
194          @returns: A provider authentication policy response from the 
195              data that was supplied with the C{id_res} response or None 
196              if the provider sent no signed PAPE response arguments. 
197          """ 
198          self = cls() 
199   
200           
201          args = success_response.getSignedNS(self.ns_uri) 
202   
203           
204           
205          if args is not None: 
206              self.parseExtensionArgs(args) 
207              return self 
208          else: 
209              return None 
 210   
212          """Parse the provider authentication policy arguments into the 
213          internal state of this object 
214   
215          @param args: unqualified provider authentication policy 
216              arguments 
217   
218          @param strict: Whether to raise an exception when bad data is 
219              encountered 
220   
221          @returns: None. The data is parsed into the internal fields of 
222              this object. 
223          """ 
224          policies_str = args.get('auth_policies') 
225          if policies_str and policies_str != 'none': 
226              self.auth_policies = policies_str.split(' ') 
227   
228          nist_level_str = args.get('nist_auth_level') 
229          if nist_level_str: 
230              try: 
231                  nist_level = int(nist_level_str) 
232              except ValueError: 
233                  if strict: 
234                      raise ValueError('nist_auth_level must be an integer between ' 
235                                       'zero and four, inclusive') 
236                  else: 
237                      self.nist_auth_level = None 
238              else: 
239                  if 0 <= nist_level < 5: 
240                      self.nist_auth_level = nist_level 
241   
242          auth_time = args.get('auth_time') 
243          if auth_time: 
244              if TIME_VALIDATOR.match(auth_time): 
245                  self.auth_time = auth_time 
246              elif strict: 
247                  raise ValueError("auth_time must be in RFC3339 format") 
 248   
249      fromSuccessResponse = classmethod(fromSuccessResponse) 
250   
252          """@see: C{L{Extension.getExtensionArgs}} 
253          """ 
254          if len(self.auth_policies) == 0: 
255              ns_args = { 
256                  'auth_policies':'none', 
257              } 
258          else: 
259              ns_args = { 
260                  'auth_policies':' '.join(self.auth_policies), 
261                  } 
262   
263          if self.nist_auth_level is not None: 
264              if self.nist_auth_level not in range(0, 5): 
265                  raise ValueError('nist_auth_level must be an integer between ' 
266                                   'zero and four, inclusive') 
267              ns_args['nist_auth_level'] = str(self.nist_auth_level) 
268   
269          if self.auth_time is not None: 
270              if not TIME_VALIDATOR.match(self.auth_time): 
271                  raise ValueError('auth_time must be in RFC3339 format') 
272   
273              ns_args['auth_time'] = self.auth_time 
274   
275          return ns_args 
  276   
277  Response.ns_uri = ns_uri 
278