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    public $ns_alias = 'pape';
38    public $ns_uri = Auth_OpenID_PAPE_NS_URI;
39
40    private $max_auth_age = 0;
41    private $preferred_auth_policies = [];
42
43    function __construct($preferred_auth_policies=null,
44                                      $max_auth_age=null)
45    {
46        if ($preferred_auth_policies === null) {
47            $preferred_auth_policies = [];
48        }
49
50        $this->preferred_auth_policies = $preferred_auth_policies;
51        $this->max_auth_age = $max_auth_age;
52    }
53
54    /**
55     * Add an acceptable authentication policy URI to this request
56     *
57     * This method is intended to be used by the relying party to add
58     * acceptable authentication types to the request.
59     *
60     * policy_uri: The identifier for the preferred type of
61     * authentication.
62     *
63     * @param string $policy_uri
64     */
65    function addPolicyURI($policy_uri)
66    {
67        if (!in_array($policy_uri, $this->preferred_auth_policies)) {
68            $this->preferred_auth_policies[] = $policy_uri;
69        }
70    }
71
72    /**
73     * Get the string arguments that should be added to an OpenID
74     * message for this extension.
75     *
76     * @param Auth_OpenID_Request|null $request
77     * @return null
78     */
79    function getExtensionArgs($request = null)
80    {
81        $ns_args = [
82            'preferred_auth_policies' => implode(' ', $this->preferred_auth_policies),
83        ];
84
85        if ($this->max_auth_age !== null) {
86            $ns_args['max_auth_age'] = strval($this->max_auth_age);
87        }
88
89        return $ns_args;
90    }
91
92    /**
93     * Instantiate a Request object from the arguments in a checkid_*
94     * OpenID message
95     *
96     * @param Auth_OpenID_Request $request
97     * @return Auth_OpenID_PAPE_Request|null
98     */
99    static function fromOpenIDRequest($request)
100    {
101        $obj = new Auth_OpenID_PAPE_Request();
102        $args = $request->message->getArgs(Auth_OpenID_PAPE_NS_URI);
103
104        if ($args === null || $args === []) {
105            return null;
106        }
107
108        $obj->parseExtensionArgs($args);
109        return $obj;
110    }
111
112    /**
113     * Set the state of this request to be that expressed in these
114     * PAPE arguments
115     *
116     * @param args: The PAPE arguments without a namespace
117     */
118    function parseExtensionArgs($args)
119    {
120        // preferred_auth_policies is a space-separated list of policy
121        // URIs
122        $this->preferred_auth_policies = [];
123
124        $policies_str = Auth_OpenID::arrayGet($args, 'preferred_auth_policies');
125        if ($policies_str) {
126            foreach (explode(' ', $policies_str) as $uri) {
127                if (!in_array($uri, $this->preferred_auth_policies)) {
128                    $this->preferred_auth_policies[] = $uri;
129                }
130            }
131        }
132
133        // max_auth_age is base-10 integer number of seconds
134        $max_auth_age_str = Auth_OpenID::arrayGet($args, 'max_auth_age');
135        if ($max_auth_age_str) {
136            $this->max_auth_age = Auth_OpenID::intval($max_auth_age_str);
137        } else {
138            $this->max_auth_age = null;
139        }
140    }
141
142    /**
143     * Given a list of authentication policy URIs that a provider
144     * supports, this method returns the subsequence of those types
145     * that are preferred by the relying party.
146     *
147     * @param supported_types: A sequence of authentication policy
148     * type URIs that are supported by a provider
149     *
150     * @return array The sub-sequence of the supported types that are
151     * preferred by the relying party. This list will be ordered in
152     * the order that the types appear in the supported_types
153     * sequence, and may be empty if the provider does not prefer any
154     * of the supported authentication types.
155     */
156    function preferredTypes($supported_types)
157    {
158        $result = [];
159
160        foreach ($supported_types as $st) {
161            if (in_array($st, $this->preferred_auth_policies)) {
162                $result[] = $st;
163            }
164        }
165        return $result;
166    }
167}
168
169/**
170 * A Provider Authentication Policy response, sent from a provider to
171 * a relying party
172 */
173class Auth_OpenID_PAPE_Response extends Auth_OpenID_Extension {
174
175    public $ns_alias = 'pape';
176    public $ns_uri = Auth_OpenID_PAPE_NS_URI;
177
178    private $auth_time = 0;
179    private $nist_auth_level = 0;
180    private $auth_policies = [];
181
182    function __construct($auth_policies=null, $auth_time=null,
183                                       $nist_auth_level=null)
184    {
185        if ($auth_policies) {
186            $this->auth_policies = $auth_policies;
187        } else {
188            $this->auth_policies = [];
189        }
190
191        $this->auth_time = $auth_time;
192        $this->nist_auth_level = $nist_auth_level;
193    }
194
195    /**
196     * Add a authentication policy to this response
197     *
198     * This method is intended to be used by the provider to add a
199     * policy that the provider conformed to when authenticating the
200     * user.
201     *
202     * @param policy_uri: The identifier for the preferred type of
203     * authentication.
204     */
205    function addPolicyURI($policy_uri)
206    {
207        if (!in_array($policy_uri, $this->auth_policies)) {
208            $this->auth_policies[] = $policy_uri;
209        }
210    }
211
212    /**
213     * Create an Auth_OpenID_PAPE_Response object from a successful
214     * OpenID library response.
215     *
216     * @param Auth_OpenID_SuccessResponse $success_response A SuccessResponse
217     * from Auth_OpenID_Consumer::complete()
218     *
219     * @return Auth_OpenID_PAPE_Response A provider authentication policy response from the
220     * data that was supplied with the id_res response.
221     */
222    static function fromSuccessResponse($success_response)
223    {
224        $obj = new Auth_OpenID_PAPE_Response();
225
226        // PAPE requires that the args be signed.
227        $args = $success_response->getSignedNS(Auth_OpenID_PAPE_NS_URI);
228
229        if ($args === null || $args === []) {
230            return null;
231        }
232
233        $result = $obj->parseExtensionArgs($args);
234
235        if ($result === false) {
236            return null;
237        } else {
238            return $obj;
239        }
240    }
241
242    /**
243     * Parse the provider authentication policy arguments into the
244     *  internal state of this object
245     *
246     * @param array $args unqualified provider authentication policy
247     * arguments
248     *
249     * @param bool $strict Whether to return false when bad data is
250     * encountered
251     *
252     * @return null|bool The data is parsed into the internal fields of
253     * this object.
254    */
255    function parseExtensionArgs($args, $strict=false)
256    {
257        $policies_str = Auth_OpenID::arrayGet($args, 'auth_policies');
258        if ($policies_str && $policies_str != "none") {
259            $this->auth_policies = explode(" ", $policies_str);
260        }
261
262        $nist_level_str = Auth_OpenID::arrayGet($args, 'nist_auth_level');
263        if ($nist_level_str !== null) {
264            $nist_level = Auth_OpenID::intval($nist_level_str);
265
266            if ($nist_level === false) {
267                if ($strict) {
268                    return false;
269                } else {
270                    $nist_level = null;
271                }
272            }
273
274            if (0 <= $nist_level && $nist_level < 5) {
275                $this->nist_auth_level = $nist_level;
276            } else if ($strict) {
277                return false;
278            }
279        }
280
281        $auth_time = Auth_OpenID::arrayGet($args, 'auth_time');
282        if ($auth_time !== null) {
283            if (preg_match(PAPE_TIME_VALIDATOR, $auth_time)) {
284                $this->auth_time = $auth_time;
285            } else if ($strict) {
286                return false;
287            }
288        }
289        return null;
290    }
291
292    /**
293     * Get the string arguments that should be added to an OpenID
294     * message for this extension.
295     *
296     * @param Auth_OpenID_Request|null $request
297     * @return null
298     */
299    function getExtensionArgs($request = null)
300    {
301        $ns_args = [];
302        if (count($this->auth_policies) > 0) {
303            $ns_args['auth_policies'] = implode(' ', $this->auth_policies);
304        } else {
305            $ns_args['auth_policies'] = 'none';
306        }
307
308        if ($this->nist_auth_level !== null) {
309            if (!in_array($this->nist_auth_level, range(0, 4), true)) {
310                return false;
311            }
312            $ns_args['nist_auth_level'] = strval($this->nist_auth_level);
313        }
314
315        if ($this->auth_time !== null) {
316            if (!preg_match(PAPE_TIME_VALIDATOR, $this->auth_time)) {
317                return false;
318            }
319
320            $ns_args['auth_time'] = $this->auth_time;
321        }
322
323        return $ns_args;
324    }
325}
326
327