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: namespace SimpleID\Protocols\OAuth;
23:
24: use SimpleID\Crypt\Random;
25: use SimpleID\Store\StoreManager;
26:
27: /**
28: * A class representing an OAuth authorization code.
29: *
30: * A new authorization code is created using the {@link create()} static
31: * method.
32: *
33: * An existing authorization code (which may or may not be valid) can be
34: * decoded using the {@link decode()} static method. You should always
35: * use the {@link isValid()} method to check whether the authorization code
36: * is valid before using it to issue tokens, etc.
37: */
38: class Code implements TokenGrantType {
39: /**
40: * Separator between the authorization code and the fully-qualified
41: * authorisation ID.
42: *
43: * @see Authorization
44: */
45: const CODE_SEPARATOR = '.';
46:
47: /** @var string */
48: private $cid;
49:
50: /** @var string */
51: private $aid;
52:
53: /** @var string */
54: private $auth_state;
55:
56: /** @var string|null */
57: private $redirect_uri;
58:
59: /** @var array<string> */
60: private $scope;
61:
62: /** @var int */
63: private $expires;
64:
65: /** @var array<string, mixed> */
66: private $additional;
67:
68: /** @var bool */
69: private $is_valid = false;
70:
71: /**
72: * Creates an authorization code. This is used by this class's static
73: * methods to create an instance of this class.
74: */
75: protected function __construct() {
76:
77: }
78:
79: /**
80: * Decodes an existing authorization code.
81: *
82: * Note that this method does not check whether the authorization
83: * code is valid issued or has not been revoked. You should always
84: * use the {@link isValid()} method to check the validity of the
85: * authorization code before using it to issue tokens.
86: *
87: * @param string $code the authorization code
88: * @return Code|null the authorization code object
89: */
90: static public function decode($code) {
91: $results = self::load($code);
92: if ($results == null) {
93: $results = new Code();
94: list($results->cid, $fqaid) = explode(self::CODE_SEPARATOR, $code, 2);
95: list($results->auth_state, $results->aid) = explode(Authorization::AUTH_STATE_SEPARATOR, $fqaid, 2);
96: $results->is_valid = false;
97: }
98:
99: return $results;
100: }
101:
102: /**
103: * Loads an existing authorization code.
104: *
105: * @param string $code the authorization code
106: * @return Code|null the authorization code object
107: */
108: static protected function load($code) {
109: $cache = \Cache::instance();
110:
111: if (!$cache->exists($code . '.code')) return null;
112:
113: /** @var Code $payload */
114: $payload = $cache->get($code . '.code');
115: if ($payload->expires < time()) return null;
116: $payload->is_valid = true;
117: return $payload;
118: }
119:
120: /**
121: * Creates an authorization code.
122: *
123: * Once the authorization code object has been created, the code can be retrieved using
124: * the {@link getCode()} method.
125: *
126: * @param Authorization $authorization the authorization that wishes to generate
127: * this code
128: * @param string|null $redirect_uri the redirect_uri parameter in the authorisation request, if
129: * present
130: * @param array<string>|string $scope the allowed scope - this should be a subset of the scope provided by the
131: * authorization
132: * @param array<string, mixed> $additional additional data to be stored in the authorization code
133: * @return Code the authorization code object
134: */
135: static public function create($authorization, $redirect_uri, $scope, $additional = []) {
136: $code = new Code();
137: $rand = new Random();
138: $cache = \Cache::instance();
139:
140: $code->cid = $rand->id();
141: $code->aid = $authorization->getStoreID();
142: $code->auth_state = $authorization->getAuthState();
143: $code->redirect_uri = $redirect_uri;
144: $code->scope = (!is_array($scope)) ? explode(' ', $scope) : $scope;
145: $code->additional = $additional;
146: $code->expires = time() + SIMPLEID_INSTANT_TOKEN_EXPIRES_IN;
147:
148: $cache->set($code->getCode() . '.code', $code, SIMPLEID_INSTANT_TOKEN_EXPIRES_IN);
149:
150: $code->is_valid = true;
151:
152: return $code;
153: }
154:
155: /**
156: * Determine whether an authorization code is valid.
157: *
158: * An authorization code is valid if:
159: *
160: * - it is created directly by the {@link create()} static method; or
161: * - it has been decoded using the {@link decode()} static method, and
162: * has been validated by SimpleID.
163: *
164: * @return bool true if the authorization code is valid
165: */
166: public function isValid() {
167: return $this->is_valid;
168: }
169:
170: /**
171: * Returns the authorization code.
172: *
173: * @return string the authorization code
174: */
175: public function getCode() {
176: return $this->cid . self::CODE_SEPARATOR . $this->auth_state . Authorization::AUTH_STATE_SEPARATOR . $this->aid;
177: }
178:
179: /**
180: * Returns the OAuth authorization that generated this code.
181: *
182: * If the authorization has been revoked, or is otherwise invalid, since
183: * the authorization code has been issued, this function will return
184: * `null`.
185: *
186: * @return Authorization|null the OAuth authorization or `null`.
187: */
188: public function getAuthorization() {
189: $store = StoreManager::instance();
190: /** @var Authorization $authorization */
191: $authorization = $store->loadAuth($this->aid);
192: if ($authorization == null) return null;
193: if ($authorization->getAuthState() != $this->auth_state) return null;
194: return $authorization;
195: }
196:
197: /**
198: * Returns the redirect_uri bound to this authorization code.
199: *
200: * If a `redirect_uri` parameter is present in the authorization request,
201: * then the authorization code is bound to that `redirect_uri`. This
202: * redirect_uri, if present, must be checked against the token request
203: * before an access token can be issued.
204: *
205: * If a `redirect_uri` parameter is not present in the authorization request,
206: * i.e. the pre-registered redirect URI is used, then this function will
207: * return `null`.
208: *
209: * @return string the redirect URI
210: */
211: public function getRedirectURI() {
212: return $this->redirect_uri;
213: }
214:
215: /**
216: * Returns the scope authorised by this authorization code. Access tokens
217: * issued from this authorization code must have this scope.
218: *
219: * @return array<string> the scope
220: */
221: public function getScope() {
222: return $this->scope;
223: }
224:
225: /**
226: * Returns additional data associated with this authorization code.
227: *
228: * @return array<string, mixed> the additional data
229: */
230: public function getAdditional() {
231: return $this->additional;
232: }
233:
234: /**
235: * Deletes the authorization code from the cache, rendering it invalid.
236: *
237: * @return void
238: */
239: public function clear() {
240: $cache = \Cache::instance();
241: $cache->clear($this->getCode() . '.code');
242: }
243:
244: public function getGrantRef() {
245: return substr($this->cid, -9);
246: }
247: }
248:
249: ?>