1: <?php
2: /*
3: * SimpleID
4: *
5: * Copyright (C) Kelvin Mo 2014-2026
6: *
7: * This program is free software; you can redistribute it and/or
8: * modify it under the terms of the GNU General Public
9: * License as published by the Free Software Foundation; either
10: * version 2 of the License, or (at your option) any later version.
11: *
12: * This program is distributed in the hope that it will be useful,
13: * but WITHOUT ANY WARRANTY; without even the implied warranty of
14: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15: * General Public License for more details.
16: *
17: * You should have received a copy of the GNU General Public
18: * License along with this program; if not, write to the Free
19: * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20: *
21: */
22:
23: namespace SimpleID\Protocols\OpenID;
24:
25: /**
26: * Class representing an OpenID request.
27: */
28: class Request extends Message {
29:
30: /**
31: * Constant for the OP-local identifier which indicates that SimpleID should choose an identifier
32: *
33: * @link http://openid.net/specs/openid-authentication-2_0.html#anchor27
34: */
35: const OPENID_IDENTIFIER_SELECT = 'http://specs.openid.net/auth/2.0/identifier_select';
36:
37: /**
38: * Constructs a new OpenID request.
39: *
40: * @param array<string, string> $request the request in array form
41: */
42: public function __construct($request) {
43: $this->container = $request;
44:
45: foreach ($request as $key => $value) {
46: if (strpos($key, 'openid.ns.') === 0) {
47: $alias = substr($key, 10);
48: $this->extension_map[$value] = $alias;
49: }
50: }
51:
52: if (!isset($this->container['openid.ns'])) {
53: $this->version = Message::OPENID_VERSION_1_1;
54: } elseif ($this->container['openid.ns'] != Message::OPENID_NS_2_0) {
55: $this->version = Message::OPENID_VERSION_1_1;
56: } else {
57: $this->version = Message::OPENID_VERSION_2;
58: }
59: }
60:
61: /**
62: * Gets the realm from the OpenID request. This is specified differently
63: * depending on the OpenID version.
64: *
65: * @return string the realm URI
66: */
67: public function getRealm() {
68: $version = $this->getVersion();
69:
70: if ($version == Message::OPENID_VERSION_1_1) {
71: $realm = $this->container['openid.trust_root'];
72: }
73:
74: if ($version == Message::OPENID_VERSION_2) {
75: $realm = $this->container['openid.realm'];
76: }
77:
78: if (!isset($realm)) {
79: $realm = $this->container['openid.return_to'];
80: }
81:
82: return $realm;
83: }
84:
85: /**
86: * Determines whether the openid.return_to address matches a realm.
87: *
88: * A URL matches a realm if:
89: *
90: * 1. The URL scheme and port of the URL are identical to those in the realm.
91: * See RFC 3986, section 3.1 for rules about URI matching.
92: * 2. The URL's path is equal to or a sub-directory of the realm's path.
93: * 3. Either:
94: * (a) The realm's domain contains the wild-card characters "*.", and the
95: * trailing part of the URL's domain is identical to the part of the
96: * realm following the "*." wildcard, or
97: * (b) The URL's domain is identical to the realm's domain
98: *
99: * @param string $realm the realm
100: * @param bool $strict whether the scheme also needs to match
101: * @return bool true if the URL matches the realm
102: * @since 0.6
103: */
104: function returnToMatches($realm, $strict = true) {
105: $url = parse_url($this->container['openid.return_to']);
106: $realm = parse_url($realm);
107: if ($url == false) return false;
108: if ($realm == false) return false;
109: if (!isset($url['scheme']) || !isset($realm['scheme'])) return false;
110:
111: foreach(['user', 'pass', 'fragment'] as $key) {
112: if (array_key_exists($key, $url) || array_key_exists($key, $realm))
113: return false;
114: }
115:
116: if ($url['scheme'] != $realm['scheme']) {
117: if ($strict) return false;
118: if ($url['scheme'] != 'https') return false;
119: if ($realm['scheme'] != 'http') return false;
120: }
121:
122: if (!isset($url['port']))
123: $url['port'] = '';
124: if (!isset($realm['port']))
125: $realm['port'] = '';
126: if (($url['port'] != $realm['port']))
127: return false;
128:
129: if (!isset($url['host']))
130: $url['host'] = '';
131: if (!isset($realm['host']))
132: $realm['host'] = '';
133:
134: $realm['host'] = strval($realm['host']);
135: if (substr($realm['host'], 0, 2) == '*.') {
136: $realm_re = '/^([^.]+\.)?' . preg_quote(substr($realm['host'], 2)) . '$/i';
137: } else {
138: $realm_re = '/^' . preg_quote($realm['host']) . '$/i';
139: }
140:
141: $url['host'] = strval($url['host']);
142: if (!preg_match($realm_re, $url['host'])) return false;
143:
144: if (!isset($url['path'])) {
145: $url['path'] = '';
146: } else {
147: $url['path'] = strval($url['path']);
148: }
149: if (!isset($realm['path'])) {
150: $realm['path'] = '';
151: } else {
152: $realm['path'] = strval($realm['path']);
153: }
154: if (substr($realm['path'], -1) == '/') $realm['path'] = substr($realm['path'], 0, -1);
155: if (($url['path'] != $realm['path']) && !preg_match('#^' . preg_quote($realm['path']) . '/.*$#', $url['path'])) return false;
156:
157: return true;
158: }
159:
160: /**
161: * Calculates the base string from which an OpenID signature is generated.
162: *
163: * @return string|null the signature base string
164: * @link http://openid.net/specs/openid-authentication-2_0.html#anchor11
165: */
166: public function getSignatureBaseString() {
167: if (!isset($this->container['openid.signed'])) return null;
168: $signed_fields = explode(',', $this->container['openid.signed']);
169: return $this->buildSignatureBaseString($signed_fields, 'openid.');
170: }
171:
172: /**
173: * Returns the base string from which an OpenID signature is generated
174: *
175: * @return string the base string
176: */
177: protected function getPrefix() {
178: return 'openid.';
179: }
180:
181: /**
182: * Returns a string representation of the request.
183: *
184: * @return string
185: */
186: public function toString() {
187: return str_replace(array('+', '%7E'), array('%20', '~'), http_build_query($this->container));
188: }
189: }
190:
191: ?>