1: <?php
2: /*
3: * SimpleID
4: *
5: * Copyright (C) Kelvin Mo 2014-2025
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:
110: foreach(['user', 'pass', 'fragment'] as $key) {
111: if (array_key_exists($key, $url) || array_key_exists($key, $realm))
112: return false;
113: }
114:
115: if ($url['scheme'] != $realm['scheme']) {
116: if ($strict) return false;
117: if ($url['scheme'] != 'https') return false;
118: if ($realm['scheme'] != 'http') return false;
119: }
120:
121: if (!isset($url['port']))
122: $url['port'] = '';
123: if (!isset($realm['port']))
124: $realm['port'] = '';
125: if (($url['port'] != $realm['port']))
126: return false;
127:
128: if (!isset($url['host']))
129: $url['host'] = '';
130: if (!isset($realm['host']))
131: $realm['host'] = '';
132:
133: $realm['host'] = strval($realm['host']);
134: if (substr($realm['host'], 0, 2) == '*.') {
135: $realm_re = '/^([^.]+\.)?' . preg_quote(substr($realm['host'], 2)) . '$/i';
136: } else {
137: $realm_re = '/^' . preg_quote($realm['host']) . '$/i';
138: }
139:
140: $url['host'] = strval($url['host']);
141: if (!preg_match($realm_re, $url['host'])) return false;
142:
143: if (!isset($url['path'])) {
144: $url['path'] = '';
145: } else {
146: $url['path'] = strval($url['path']);
147: }
148: if (!isset($realm['path'])) {
149: $realm['path'] = '';
150: } else {
151: $realm['path'] = strval($realm['path']);
152: }
153: if (substr($realm['path'], -1) == '/') $realm['path'] = substr($realm['path'], 0, -1);
154: if (($url['path'] != $realm['path']) && !preg_match('#^' . preg_quote($realm['path']) . '/.*$#', $url['path'])) return false;
155:
156: return true;
157: }
158:
159: /**
160: * Calculates the base string from which an OpenID signature is generated.
161: *
162: * @return string|null the signature base string
163: * @link http://openid.net/specs/openid-authentication-2_0.html#anchor11
164: */
165: public function getSignatureBaseString() {
166: if (!isset($this->container['openid.signed'])) return null;
167: $signed_fields = explode(',', $this->container['openid.signed']);
168: return $this->buildSignatureBaseString($signed_fields, 'openid.');
169: }
170:
171: /**
172: * Returns the base string from which an OpenID signature is generated
173: *
174: * @return string the base string
175: */
176: protected function getPrefix() {
177: return 'openid.';
178: }
179:
180: /**
181: * Returns a string representation of the request.
182: *
183: * @return string
184: */
185: public function toString() {
186: return str_replace(array('+', '%7E'), array('%20', '~'), http_build_query($this->container));
187: }
188: }
189:
190: ?>