From 1a02afa4548a728a77f9f8a1c55c694af56fe2e1 Mon Sep 17 00:00:00 2001 From: femike Date: Mon, 26 Oct 2015 19:52:13 +0500 Subject: [PATCH] Add storage mongodb --- models/mongodb/OauthAccessTokens.php | 81 ++++ models/mongodb/OauthAuthorizationCodes.php | 85 ++++ models/mongodb/OauthClients.php | 102 +++++ models/mongodb/OauthRefreshTokens.php | 65 ++++ models/mongodb/OauthScopes.php | 47 +++ storage/Mongo.php | 429 +++++++++++++++++++++ 6 files changed, 809 insertions(+) create mode 100644 models/mongodb/OauthAccessTokens.php create mode 100644 models/mongodb/OauthAuthorizationCodes.php create mode 100644 models/mongodb/OauthClients.php create mode 100644 models/mongodb/OauthRefreshTokens.php create mode 100644 models/mongodb/OauthScopes.php create mode 100644 storage/Mongo.php diff --git a/models/mongodb/OauthAccessTokens.php b/models/mongodb/OauthAccessTokens.php new file mode 100644 index 0000000..8169c2f --- /dev/null +++ b/models/mongodb/OauthAccessTokens.php @@ -0,0 +1,81 @@ + 40], + [['client_id'], 'string', 'max' => 32], + [['scope'], 'string', 'max' => 2000] + ]; + } + + /** + * @inheritdoc + */ + public function attributeLabels() + { + return [ + 'access_token' => 'Access Token', + 'client_id' => 'Client ID', + 'user_id' => 'User ID', + 'expires' => 'Expires', + 'scope' => 'Scope', + ]; + } + + /** + * @return \yii\db\ActiveQuery + */ + public function getClient() + { + return $this->hasOne(OauthClients::className(), ['client_id' => 'client_id']); + } +} \ No newline at end of file diff --git a/models/mongodb/OauthAuthorizationCodes.php b/models/mongodb/OauthAuthorizationCodes.php new file mode 100644 index 0000000..5141eca --- /dev/null +++ b/models/mongodb/OauthAuthorizationCodes.php @@ -0,0 +1,85 @@ + 40], + [['client_id'], 'string', 'max' => 32], + [['redirect_uri'], 'string', 'max' => 1000], + [['scope'], 'string', 'max' => 2000] + ]; + } + + /** + * @inheritdoc + */ + public function attributeLabels() + { + return [ + 'authorization_code' => 'Authorization Code', + 'client_id' => 'Client ID', + 'user_id' => 'User ID', + 'redirect_uri' => 'Redirect Uri', + 'expires' => 'Expires', + 'scope' => 'Scope', + ]; + } + + /** + * @return \yii\db\ActiveQuery + */ + public function getClient() + { + return $this->hasOne(OauthClients::className(), ['client_id' => 'client_id']); + } +} \ No newline at end of file diff --git a/models/mongodb/OauthClients.php b/models/mongodb/OauthClients.php new file mode 100644 index 0000000..5833178 --- /dev/null +++ b/models/mongodb/OauthClients.php @@ -0,0 +1,102 @@ + 32], + [['redirect_uri'], 'string', 'max' => 1000], + [['grant_types'], 'string', 'max' => 100], + [['scope'], 'string', 'max' => 2000] + ]; + } + + /** + * @inheritdoc + */ + public function attributeLabels() + { + return [ + 'client_id' => 'Client ID', + 'client_secret' => 'Client Secret', + 'redirect_uri' => 'Redirect Uri', + 'grant_types' => 'Grant Types', + 'scope' => 'Scope', + 'user_id' => 'User ID', + ]; + } + + /** + * @return \yii\db\ActiveQuery + */ + public function getOauthAccessTokens() + { + return $this->hasMany(OauthAccessTokens::className(), ['client_id' => 'client_id']); + } + + /** + * @return \yii\db\ActiveQuery + */ + public function getOauthAuthorizationCodes() + { + return $this->hasMany(OauthAuthorizationCodes::className(), ['client_id' => 'client_id']); + } + + /** + * @return \yii\db\ActiveQuery + */ + public function getOauthRefreshTokens() + { + return $this->hasMany(OauthRefreshTokens::className(), ['client_id' => 'client_id']); + } +} \ No newline at end of file diff --git a/models/mongodb/OauthRefreshTokens.php b/models/mongodb/OauthRefreshTokens.php new file mode 100644 index 0000000..785f859 --- /dev/null +++ b/models/mongodb/OauthRefreshTokens.php @@ -0,0 +1,65 @@ + 40], + [['client_id'], 'string', 'max' => 32], + [['scope'], 'string', 'max' => 2000] + ]; + } + + /** + * @inheritdoc + */ + public function attributeLabels() + { + return [ + 'refresh_token' => 'Refresh Token', + 'client_id' => 'Client ID', + 'user_id' => 'User ID', + 'expires' => 'Expires', + 'scope' => 'Scope', + ]; + } + + /** + * @return \yii\db\ActiveQuery + */ + public function getClient() + { + return $this->hasOne(OauthClients::className(), ['client_id' => 'client_id']); + } +} \ No newline at end of file diff --git a/models/mongodb/OauthScopes.php b/models/mongodb/OauthScopes.php new file mode 100644 index 0000000..7d86aec --- /dev/null +++ b/models/mongodb/OauthScopes.php @@ -0,0 +1,47 @@ + 2000] + ]; + } + + /** + * @inheritdoc + */ + public function attributeLabels() + { + return [ + 'scope' => 'Scope', + 'is_default' => 'Is Default', + ]; + } +} \ No newline at end of file diff --git a/storage/Mongo.php b/storage/Mongo.php new file mode 100644 index 0000000..06d93d1 --- /dev/null +++ b/storage/Mongo.php @@ -0,0 +1,429 @@ +connection !== null && \Yii::$app->has($this->connection)) { + $db = \Yii::$app->get($this->connection); + if (!($db instanceof \yii\mongodb\Connection)) { + throw new \yii\base\InvalidConfigException('Connection component must implement \yii\mongodb\Connection.'); + } + + $connection = $db->getDatabase()->mongoDb; + } + } else { + $connection = new \yii\mongodb\Connection([ + 'dsn' => $this->dsn, + ]); + $connection = $connection->getDatabase()->mongoDb; + } + + parent::__construct($connection, $config); + } + + /* ClientCredentialsInterface */ + public function checkClientCredentials($client_id, $client_secret = null) + { + if ($result = $this->collection('client_table')->findOne([ + '$or' => [ + ['client_id' => $client_id], + ['client_id' => new \MongoId($client_id)] + ] + ]) + ) { + return $result['client_secret'] == $client_secret; + } + + return false; + } + + public function isPublicClient($client_id) + { + if (!$result = $this->collection('client_table')->findOne([ + '$or' => [ + ['client_id' => $client_id], + ['client_id' => new \MongoId($client_id)] + ] + ]) + ) { + return false; + } + + return empty($result['client_secret']); + } + + /* ClientInterface */ + public function getClientDetails($client_id) + { + $result = $this->collection('client_table')->findOne([ + '$or' => [ + ['client_id' => $client_id], + ['client_id' => new \MongoId($client_id)] + ] + ]); + + return is_null($result) ? false : $result; + } + + public function setClientDetails($client_id, $client_secret = null, $redirect_uri = null, $grant_types = null, $scope = null, $user_id = null) + { + if ($this->getClientDetails($client_id)) { + $this->collection('client_table')->update( + ['client_id' => $client_id], + [ + '$set' => [ + 'client_secret' => $client_secret, + 'redirect_uri' => $redirect_uri, + 'grant_types' => $grant_types, + 'scope' => $scope, + 'user_id' => $user_id, + ] + ] + ); + } else { + $client = [ + 'client_id' => $client_id, + 'client_secret' => $client_secret, + 'redirect_uri' => $redirect_uri, + 'grant_types' => $grant_types, + 'scope' => $scope, + 'user_id' => $user_id, + ]; + $this->collection('client_table')->insert($client); + } + + return true; + } + + public function checkRestrictedGrantType($client_id, $grant_type) + { + $details = $this->getClientDetails($client_id); + if (isset($details['grant_types'])) { + $grant_types = explode(' ', $details['grant_types']); + + return in_array($grant_type, $grant_types); + } + + // if grant_types are not defined, then none are restricted + return true; + } + + /* AccessTokenInterface */ + public function getAccessToken($access_token) + { + $token = $this->collection('access_token_table')->findOne(['access_token' => $access_token]); + + return is_null($token) ? false : $token; + } + + public function setAccessToken($access_token, $client_id, $user_id, $expires, $scope = null) + { + // if it exists, update it. + if ($this->getAccessToken($access_token)) { + $this->collection('access_token_table')->update( + ['access_token' => $access_token], + [ + '$set' => [ + 'client_id' => $client_id, + 'expires' => $expires, + 'user_id' => $user_id, + 'scope' => $scope + ] + ] + ); + } else { + $token = [ + 'access_token' => $access_token, + 'client_id' => $client_id, + 'expires' => $expires, + 'user_id' => $user_id, + 'scope' => $scope + ]; + $this->collection('access_token_table')->insert($token); + } + + return true; + } + + public function unsetAccessToken($access_token) + { + $this->collection('access_token_table')->remove(['access_token' => $access_token]); + } + + /* AuthorizationCodeInterface */ + public function getAuthorizationCode($code) + { + $code = $this->collection('code_table')->findOne(['authorization_code' => $code]); + + return is_null($code) ? false : $code; + } + + public function setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null) + { + // if it exists, update it. + if ($this->getAuthorizationCode($code)) { + $this->collection('code_table')->update( + ['authorization_code' => $code], + [ + '$set' => [ + 'client_id' => $client_id, + 'user_id' => $user_id, + 'redirect_uri' => $redirect_uri, + 'expires' => $expires, + 'scope' => $scope, + 'id_token' => $id_token, + ] + ] + ); + } else { + $token = [ + 'authorization_code' => $code, + 'client_id' => $client_id, + 'user_id' => $user_id, + 'redirect_uri' => $redirect_uri, + 'expires' => $expires, + 'scope' => $scope, + 'id_token' => $id_token, + ]; + $this->collection('code_table')->insert($token); + } + + return true; + } + + public function expireAuthorizationCode($code) + { + $this->collection('code_table')->remove(['authorization_code' => $code]); + + return true; + } + + /* UserCredentialsInterface */ + public function checkUserCredentials($username, $password) + { + if ($user = $this->getUser($username)) { + return $this->checkPassword($user, $password); + } + + return false; + } + + public function getUserDetails($username) + { + if ($user = $this->getUser($username)) { + $user['user_id'] = $user['username']; + } + + return $user; + } + + /* RefreshTokenInterface */ + public function getRefreshToken($refresh_token) + { + $token = $this->collection('refresh_token_table')->findOne(['refresh_token' => $refresh_token]); + + return is_null($token) ? false : $token; + } + + public function setRefreshToken($refresh_token, $client_id, $user_id, $expires, $scope = null) + { + $token = [ + 'refresh_token' => $refresh_token, + 'client_id' => $client_id, + 'user_id' => $user_id, + 'expires' => $expires, + 'scope' => $scope + ]; + $this->collection('refresh_token_table')->insert($token); + + return true; + } + + public function unsetRefreshToken($refresh_token) + { + $this->collection('refresh_token_table')->remove(['refresh_token' => $refresh_token]); + + return true; + } + + // plaintext passwords are bad! Override this for your application + protected function checkPassword($user, $password) + { + return $user['password'] == $password; + } + + public function getUser($username) + { + $result = $this->collection('user_table')->findOne(['username' => $username]); + + return is_null($result) ? false : $result; + } + + public function setUser($username, $password, $firstName = null, $lastName = null) + { + if ($this->getUser($username)) { + $this->collection('user_table')->update( + ['username' => $username], + [ + '$set' => [ + 'password' => $password, + 'first_name' => $firstName, + 'last_name' => $lastName + ] + ] + ); + } else { + $user = [ + 'username' => $username, + 'password' => $password, + 'first_name' => $firstName, + 'last_name' => $lastName + ]; + $this->collection('user_table')->insert($user); + } + + return true; + } + + public function getClientKey($client_id, $subject) + { + $result = $this->collection('jwt_table')->findOne([ + '$or' => [ + [ + 'client_id' => $client_id, + 'subject' => $subject], + [ + 'client_id' => new \MongoId($client_id), + 'subject' => $subject + ] + ] + ]); + + return is_null($result) ? false : $result['key']; + } + + public function getClientScope($client_id) + { + if (!$clientDetails = $this->getClientDetails($client_id)) { + return false; + } + + if (isset($clientDetails['scope'])) { + return $clientDetails['scope']; + } + + return null; + } + + public function getJti($client_id, $subject, $audience, $expiration, $jti) + { + //TODO: Needs mongodb implementation. + throw new \Exception('getJti() for the MongoDB driver is currently unimplemented.'); + } + + public function setJti($client_id, $subject, $audience, $expiration, $jti) + { + //TODO: Needs mongodb implementation. + throw new \Exception('setJti() for the MongoDB driver is currently unimplemented.'); + } + + /***** @TODO need tests ****** */ + /** + * @param null $client_id + * @return bool + */ + public function getPublicKey($client_id = null) + { + if (!$result = $this->collection('public_key_table')->findOne([ + '$or' => [ + ['client_id' => $client_id], + ['client_id' => new \MongoId($client_id)] + ] + ]) + ) { + return false; + } + + return $result['public_key']; + } + + public function getPrivateKey($client_id = null) + { + if (!$result = $this->collection('public_key_table')->findOne([ + '$or' => [ + ['client_id' => $client_id], + ['client_id' => new \MongoId($client_id)] + ] + ]) + ) { + return false; + } + + return $result['private_key']; + } + + /** + * SELECT count(scope) as count FROM %s WHERE scope IN (%s)', 'scope_table', $whereIn + * @param $scope + * @return bool + */ + public function scopeExists($scope) + { + $scope = explode(' ', $scope); + + if (!$result = $this->collection('scope_table')->count(['scope' => ['$in' => $scope]])) { + return false; + } + + return $result == count($scope); + } + + public function getEncryptionAlgorithm($client_id = null) + { +// $stmt = $this->db->prepare($sql = sprintf('SELECT encryption_algorithm FROM %s WHERE client_id=:client_id OR client_id IS NULL ORDER BY client_id IS NOT NULL DESC', $this->config['public_key_table'])); +// +// $stmt->execute(compact('client_id')); +// if ($result = $stmt->fetch(\PDO::FETCH_ASSOC)) { +// return $result['encryption_algorithm']; +// } +// +// return 'RS256'; + //TODO: Needs mongodb implementation. + throw new \Exception('getEncryptionAlgorithm() for the MongoDB driver is currently unimplemented.'); + } + + public function getDefaultScope($client_id = null) + { +// $stmt = $this->db->prepare(sprintf('SELECT scope FROM %s WHERE is_default=:is_default', $this->config['scope_table'])); +// $stmt->execute(array('is_default' => true)); +// +// if ($result = $stmt->fetchAll(\PDO::FETCH_ASSOC)) { +// $defaultScope = array_map(function ($row) { +// return $row['scope']; +// }, $result); +// +// return implode(' ', $defaultScope); +// } +// +// return null; + + //TODO: Needs mongodb implementation. + throw new \Exception('getDefaultScope() for the MongoDB driver is currently unimplemented.'); + } +}