1<?php
2
3/**
4 * An implementation of the OpenID Provider Authentication Policy
5 *  Extension 1.0
6 *
7 * See:
8 * http://openid.net/developers/specs/
9 */
10
11require_once "Auth/OpenID/Extension.php";
12
13define('Auth_OpenID_PAPE_NS_URI',
14       "http://specs.openid.net/extensions/pape/1.0");
15
16define('PAPE_AUTH_MULTI_FACTOR_PHYSICAL',
17       'http://schemas.openid.net/pape/policies/2007/06/multi-factor-physical');
18define('PAPE_AUTH_MULTI_FACTOR',
19       'http://schemas.openid.net/pape/policies/2007/06/multi-factor');
20define('PAPE_AUTH_PHISHING_RESISTANT',
21       'http://schemas.openid.net/pape/policies/2007/06/phishing-resistant');
22
23define('PAPE_TIME_VALIDATOR',
24      '/^[0-9]{4,4}-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9]Z$/');
25/**
26 * A Provider Authentication Policy request, sent from a relying party
27 * to a provider
28 *
29 * preferred_auth_policies: The authentication policies that
30 * the relying party prefers
31 *
32 * max_auth_age: The maximum time, in seconds, that the relying party
33 * wants to allow to have elapsed before the user must re-authenticate
34 */
35class Auth_OpenID_PAPE_Request extends Auth_OpenID_Extension {
36
37    var $ns_alias = 'pape';
38    var $ns_uri = Auth_OpenID_PAPE_NS_URI;
39
40    function Auth_OpenID_PAPE_Request($preferred_auth_policies=null,
41                                      $max_auth_age=null)
42    {
43        if ($preferred_auth_policies === null) {
44            $preferred_auth_policies = array();
45        }
46
47        $this->preferred_auth_policies = $preferred_auth_policies;
48        $this->max_auth_age = $max_auth_age;
49    }
50
51    /**
52     * Add an acceptable authentication policy URI to this request
53     *
54     * This method is intended to be used by the relying party to add
55     * acceptable authentication types to the request.
56     *
57     * policy_uri: The identifier for the preferred type of
58     * authentication.
59     */
60    function addPolicyURI($policy_uri)
61    {
62        if (!in_array($policy_uri, $this->preferred_auth_policies)) {
63            $this->preferred_auth_policies[] = $policy_uri;
64        }
65    }
66
67    function getExtensionArgs()
68    {
69        $ns_args = array(
70                         'preferred_auth_policies' =>
71                           implode(' ', $this->preferred_auth_policies)
72                         );
73
74        if ($this->max_auth_age !== null) {
75            $ns_args['max_auth_age'] = strval($this->max_auth_age);
76        }
77
78        return $ns_args;
79    }
80
81    /**
82     * Instantiate a Request object from the arguments in a checkid_*
83     * OpenID message
84     */
85    static function fromOpenIDRequest($request)
86    {
87        $obj = new Auth_OpenID_PAPE_Request();
88        $args = $request->message->getArgs(Auth_OpenID_PAPE_NS_URI);
89
90        if ($args === null || $args === array()) {
91            return null;
92        }
93
94        $obj->parseExtensionArgs($args);
95        return $obj;
96    }
97
98    /**
99     * Set the state of this request to be that expressed in these
100     * PAPE arguments
101     *
102     * @param args: The PAPE arguments without a namespace
103     */
104    function parseExtensionArgs($args)
105    {
106        // preferred_auth_policies is a space-separated list of policy
107        // URIs
108        $this->preferred_auth_policies = array();
109
110        $policies_str = Auth_OpenID::arrayGet($args, 'preferred_auth_policies');
111        if ($policies_str) {
112            foreach (explode(' ', $policies_str) as $uri) {
113                if (!in_array($uri, $this->preferred_auth_policies)) {
114                    $this->preferred_auth_policies[] = $uri;
115                }
116            }
117        }
118
119        // max_auth_age is base-10 integer number of seconds
120        $max_auth_age_str = Auth_OpenID::arrayGet($args, 'max_auth_age');
121        if ($max_auth_age_str) {
122            $this->max_auth_age = Auth_OpenID::intval($max_auth_age_str);
123        } else {
124            $this->max_auth_age = null;
125        }
126    }
127
128    /**
129     * Given a list of authentication policy URIs that a provider
130     * supports, this method returns the subsequence of those types
131     * that are preferred by the relying party.
132     *
133     * @param supported_types: A sequence of authentication policy
134     * type URIs that are supported by a provider
135     *
136     * @return array The sub-sequence of the supported types that are
137     * preferred by the relying party. This list will be ordered in
138     * the order that the types appear in the supported_types
139     * sequence, and may be empty if the provider does not prefer any
140     * of the supported authentication types.
141     */
142    function preferredTypes($supported_types)
143    {
144        $result = array();
145
146        foreach ($supported_types as $st) {
147            if (in_array($st, $this->preferred_auth_policies)) {
148                $result[] = $st;
149            }
150        }
151        return $result;
152    }
153}
154
155/**
156 * A Provider Authentication Policy response, sent from a provider to
157 * a relying party
158 */
159class Auth_OpenID_PAPE_Response extends Auth_OpenID_Extension {
160
161    var $ns_alias = 'pape';
162    var $ns_uri = Auth_OpenID_PAPE_NS_URI;
163
164    function Auth_OpenID_PAPE_Response($auth_policies=null, $auth_time=null,
165                                       $nist_auth_level=null)
166    {
167        if ($auth_policies) {
168            $this->auth_policies = $auth_policies;
169        } else {
170            $this->auth_policies = array();
171        }
172
173        $this->auth_time = $auth_time;
174        $this->nist_auth_level = $nist_auth_level;
175    }
176
177    /**
178     * Add a authentication policy to this response
179     *
180     * This method is intended to be used by the provider to add a
181     * policy that the provider conformed to when authenticating the
182     * user.
183     *
184     * @param policy_uri: The identifier for the preferred type of
185     * authentication.
186     */
187    function addPolicyURI($policy_uri)
188    {
189        if (!in_array($policy_uri, $this->auth_policies)) {
190            $this->auth_policies[] = $policy_uri;
191        }
192    }
193
194    /**
195     * Create an Auth_OpenID_PAPE_Response object from a successful
196     * OpenID library response.
197     *
198     * @param success_response $success_response A SuccessResponse
199     * from Auth_OpenID_Consumer::complete()
200     *
201     * @returns: A provider authentication policy response from the
202     * data that was supplied with the id_res response.
203     */
204    static function fromSuccessResponse($success_response)
205    {
206        $obj = new Auth_OpenID_PAPE_Response();
207
208        // PAPE requires that the args be signed.
209        $args = $success_response->getSignedNS(Auth_OpenID_PAPE_NS_URI);
210
211        if ($args === null || $args === array()) {
212            return null;
213        }
214
215        $result = $obj->parseExtensionArgs($args);
216
217        if ($result === false) {
218            return null;
219        } else {
220            return $obj;
221        }
222    }
223
224    /**
225     * Parse the provider authentication policy arguments into the
226     *  internal state of this object
227     *
228     * @param args: unqualified provider authentication policy
229     * arguments
230     *
231     * @param strict: Whether to return false when bad data is
232     * encountered
233     *
234     * @return null The data is parsed into the internal fields of
235     * this object.
236    */
237    function parseExtensionArgs($args, $strict=false)
238    {
239        $policies_str = Auth_OpenID::arrayGet($args, 'auth_policies');
240        if ($policies_str && $policies_str != "none") {
241            $this->auth_policies = explode(" ", $policies_str);
242        }
243
244        $nist_level_str = Auth_OpenID::arrayGet($args, 'nist_auth_level');
245        if ($nist_level_str !== null) {
246            $nist_level = Auth_OpenID::intval($nist_level_str);
247
248            if ($nist_level === false) {
249                if ($strict) {
250                    return false;
251                } else {
252                    $nist_level = null;
253                }
254            }
255
256            if (0 <= $nist_level && $nist_level < 5) {
257                $this->nist_auth_level = $nist_level;
258            } else if ($strict) {
259                return false;
260            }
261        }
262
263        $auth_time = Auth_OpenID::arrayGet($args, 'auth_time');
264        if ($auth_time !== null) {
265            if (preg_match(PAPE_TIME_VALIDATOR, $auth_time)) {
266                $this->auth_time = $auth_time;
267            } else if ($strict) {
268                return false;
269            }
270        }
271    }
272
273    function getExtensionArgs()
274    {
275        $ns_args = array();
276        if (count($this->auth_policies) > 0) {
277            $ns_args['auth_policies'] = implode(' ', $this->auth_policies);
278        } else {
279            $ns_args['auth_policies'] = 'none';
280        }
281
282        if ($this->nist_auth_level !== null) {
283            if (!in_array($this->nist_auth_level, range(0, 4), true)) {
284                return false;
285            }
286            $ns_args['nist_auth_level'] = strval($this->nist_auth_level);
287        }
288
289        if ($this->auth_time !== null) {
290            if (!preg_match(PAPE_TIME_VALIDATOR, $this->auth_time)) {
291                return false;
292            }
293
294            $ns_args['auth_time'] = $this->auth_time;
295        }
296
297        return $ns_args;
298    }
299}
300
301