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\OAuth;
24:
25: use SimpleID\Models\Client;
26:
27: /**
28: * A class representing an OAuth client.
29: */
30: class OAuthClient extends Client {
31: /** @var bool */
32: protected $dynamic = false;
33:
34: /**
35: * Creates a new OAuth client
36: *
37: * @param array<string, mixed> $data
38: */
39: public function __construct($data) {
40: parent::__construct(array_replace_recursive([
41: 'oauth' => [
42: 'token_endpoint_auth_method' => 'client_secret_basic',
43: 'response_types' => [ 'code' ],
44: 'grant_types' => [ 'authorization_code' ],
45: 'application_type' => 'web'
46: ]
47: ], $data));
48: }
49:
50: /**
51: * Returns whether a client is a confidential client.
52: *
53: * A client is a confidential client if a `client_secret` has been set,
54: * and the `token_endpoint_auth_method` (if set) is not set to `none`.
55: * This means that the client must authenticate with SimpleID using
56: * credentials which the client is able to keep secret.
57: *
58: * @return bool true if the client is confidential
59: */
60: public function isConfidential() {
61: if (isset($this->container['oauth']['token_endpoint_auth_method'])
62: && ($this->container['oauth']['token_endpoint_auth_method'] == 'none'))
63: return false;
64:
65: return (isset($this->container['oauth']['client_secret']));
66: }
67:
68: /**
69: * Returns whether the client is a native app.
70: *
71: * Native apps have `oauth.application_type` set to `native`.
72: *
73: * @return bool true if the client is a native app
74: */
75: public function isNative() {
76: return (isset($this->container['oauth']['application_type'])) ? ($this->container['oauth']['application_type'] == 'native') : false;
77: }
78:
79: /**
80: * Returns whether a specified redirect_uri has been registered
81: * with the client.
82: *
83: * Special rules apply when the client is a native app with a HTTP
84: * loopback redirect_uri - the port is ignore in the comparison.
85: *
86: * @param string $redirect_uri the redirect_uri to test
87: * @return bool true if the redirect_uri has been registered
88: */
89: public function hasRedirectUri($redirect_uri) {
90: $redirect_uri_found = false;
91:
92: $is_native = $this->isNative();
93:
94: $redirect_uri_components = parse_url($redirect_uri);
95: if ($redirect_uri_components == false) return false;
96:
97: foreach ($this->container['oauth']['redirect_uris'] as $client_redirect_uri) {
98: $client_redirect_uri_components = parse_url($client_redirect_uri);
99: if (($client_redirect_uri_components == false)
100: || !isset($client_redirect_uri_components['scheme'])
101: || !isset($client_redirect_uri_components['host'])) continue;
102:
103: // Quick check - if redirect_uri has a query component and the registered
104: // one does not
105: if (!isset($client_redirect_uri_components['query']) && isset($redirect_uri_components['query'])) continue;
106:
107: if ($is_native && (strtolower($client_redirect_uri_components['scheme']) == 'http')
108: && (($client_redirect_uri_components['host'] == '127.0.0.1') || ($client_redirect_uri_components['host'] == '[::1]'))) {
109: // For native applications with http loopback, we remove the port number
110: // before making the comparison
111: $request_redirect_uri = preg_replace('!(http://(127\.0\.0\.1|\[::1\]))(:\d+)?!', '$1', $redirect_uri);
112: } else {
113: $request_redirect_uri = $redirect_uri;
114: }
115:
116: if (strcasecmp(substr($request_redirect_uri, 0, strlen($client_redirect_uri)), $client_redirect_uri) === 0) {
117: $redirect_uri_found = true;
118: break;
119: }
120: }
121:
122: return $redirect_uri_found;
123: }
124: }
125:
126: ?>