├── .gitignore ├── README.md ├── composer.json ├── config-template └── module_oauth2.php ├── default-enable ├── hooks ├── hook_cron.php ├── hook_dbal.php └── hook_frontpage.php ├── lib └── SimpleSAML │ └── Modules │ └── OAuth2 │ ├── Entity │ ├── AccessTokenEntity.php │ ├── AuthCodeEntity.php │ ├── ClientEntity.php │ ├── RefreshTokenEntity.php │ ├── ScopeEntity.php │ └── UserEntity.php │ ├── Form │ └── ClientForm.php │ ├── OAuth2AuthorizationServer.php │ ├── OAuth2ResourceServer.php │ └── Repositories │ ├── AbstractDBALRepository.php │ ├── AccessTokenRepository.php │ ├── AuthCodeRepository.php │ ├── ClientRepository.php │ ├── RefreshTokenRepository.php │ ├── ScopeRepository.php │ └── UserRepository.php ├── templates ├── registry_edit.twig ├── registry_list.twig └── registry_new.twig └── www ├── access_token.php ├── authorize.php ├── registry.edit.php ├── registry.new.php ├── registry.php ├── resources └── style.css └── userinfo.php /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | composer.lock 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SimpleSAMLphp OAuth2 module 2 | ==================================== 3 | 4 | ## What is new? 5 | 6 | From versions 1.2.x and 2.2.x this modules supports diferents AuthSources by 7 | client, instead one for all. If you come from versions <1.2 and <2.2 you must 8 | update the schema. Please, see the _"Create or update the schema"_ section. 9 | 10 | ## Installation 11 | 12 | This package add support for the OAuth2 protocol through a SimpleSAMLphp module 13 | installable through [Composer](https://getcomposer.org/). Installation can be as 14 | easy as executing: 15 | 16 | ``` 17 | composer.phar require sgomez/simplesamlphp-module-oauth2 1.0.0 # for SSP < 1.14 18 | composer.phar require sgomez/simplesamlphp-module-oauth2 ~1.0 # for SSP >= 1.14 19 | composer.phar require sgomez/simplesamlphp-module-oauth2 ~2.0 # for SSP >= 2.0|master 20 | ``` 21 | 22 | ## Configuration 23 | 24 | This module requires [sgomez/simplesamlphp-module-dbal](https://github.com/sgomez/simplesamlphp-module-dbal) 25 | module configured. It's installed as a dependency but you need to read the module info and configure it. 26 | 27 | ### Create or update the schema 28 | 29 | You need to run this to create the schema using the DBAL store module: 30 | 31 | ``` 32 | bash$ vendor/bin/dbalschema 33 | ``` 34 | 35 | ### Configure the module 36 | 37 | Copy the template file to the config directory: 38 | 39 | ``` 40 | cp modules/oauth2/config-template/module_oauth2.php config/ 41 | ``` 42 | 43 | and edit it. The options are self explained. 44 | 45 | ## Create oauth2 clients 46 | 47 | To add and remove Oauth2 clients, you need to logon on simplesaml with an admin account. Open the _Federation_ tab 48 | and you will see the _OAuth2 Client Registry_ option. 49 | 50 | You can specify as many redirect address as you want. 51 | 52 | ## Using the module 53 | 54 | This module is based on [Oauth2 Server from the PHP League](https://oauth2.thephpleague.com/) and supports implicit and explicit tokens. 55 | 56 | ### Create the oauth2 keys: 57 | 58 | The oauth2 library used generates Json Web Tokens to create the Access Tokens, so you need to create a public and private cert keys: 59 | 60 | To generate the private key run this command on the terminal: 61 | 62 | ``` 63 | openssl genrsa -out cert/oauth2_module.pem 1024 64 | ``` 65 | 66 | If you want to provide a passphrase for your private key run this command instead: 67 | 68 | ``` 69 | openssl genrsa -passout pass:_passphrase_ -out cert/oauth2_module.pem 1024 70 | ``` 71 | 72 | then extract the public key from the private key: 73 | 74 | ``` 75 | openssl rsa -in cert/oauth2_module.pem -pubout -out cert/oauth2_module.crt 76 | ``` 77 | or use your passphrase if provided on private key generation: 78 | 79 | ``` 80 | openssl rsa -in cert/oauth2_module.pem -passin pass:_passphrase_ -pubout -out cert/oauth2_module.crt 81 | ``` 82 | 83 | If you use a passphrase remember to configure it in the _module_oauth2.php_ config file. 84 | 85 | ### Explicit Token 86 | 87 | To ask an explicit token see the [Authorization Code Grant](https://oauth2.thephpleague.com/authorization-server/auth-code-grant/) 88 | help page to know the parameters than you need to send (see Part One). 89 | 90 | The address to the authorization server is: _{{baseurlpath}}/module.php/oauth2/authorize.php_ 91 | 92 | Now you need to ask for an access token. See the [Part Two](https://oauth2.thephpleague.com/authorization-server/auth-code-grant/). 93 | 94 | The address to the access token server is: _{{baseurlpath}}/module.php/oauth2/access_token.php_ 95 | 96 | ### Implicit Token 97 | 98 | To ask an implicit token see the [Implicit Grant](https://oauth2.thephpleague.com/authorization-server/implicit-grant/) 99 | help page to know the parameters than you need to send. 100 | 101 | The address to the authorization server is: _{{baseurlpath}}/module.php/oauth2/authorize.php_ 102 | 103 | ### Take the attributes 104 | 105 | To recover the user attributes you need to send and `Authorization` header with the Access Token as 106 | a Bearer Token to the userinfo page: _{{baseurlpath}}/module.php/oauth2/userinfo.php_ 107 | 108 | Example: 109 | 110 | ``` 111 | curl --request GET \ 112 | --url http://server.com/simplesaml/module.php/oauth2/userinfo.php \ 113 | --header 'authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1Ni...' 114 | ``` 115 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sgomez/simplesamlphp-module-oauth2", 3 | "description": "A SimpleSAMLphp module adding support for the OAuth2 protocol.", 4 | "type": "simplesamlphp-module", 5 | "keywords": [ "oauth2" ], 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Sergio Gómez", 10 | "email": "sergio@uco.es" 11 | } 12 | ], 13 | "autoload": { 14 | "psr-0": { 15 | "SimpleSAML\\Modules\\OAuth2\\": "lib" 16 | } 17 | }, 18 | "require": { 19 | "php" : ">=5.5.9", 20 | "simplesamlphp/composer-module-installer" : "~1.0", 21 | "league/oauth2-server" : "~6.0", 22 | "nette/forms" : "~2.4", 23 | "sgomez/simplesamlphp-module-dbal" : "~2.0", 24 | "zendframework/zend-diactoros" : "~1.3" 25 | }, 26 | "require-dev": { 27 | "simplesamlphp/simplesamlphp" : "dev-master as 2.0", 28 | "simplesamlphp/saml2" : "~3.0" 29 | }, 30 | "extra": { 31 | "branch-alias": { 32 | "dev-master": "2.0.x-dev" 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /config-template/module_oauth2.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | $config = [ 12 | // The private key passphrase (optional) 13 | // 'pass_phrase' => 'secret', 14 | 15 | // Tokens TTL 16 | 'authCodeDuration' => 'PT10M', // 10 minutes 17 | 'refreshTokenDuration' => 'P1M', // 1 month 18 | 'accessTokenDuration' => 'PT1H', // 1 hour, 19 | 20 | // Tag to run storage cleanup script using the cron module... 21 | 'cron_tag' => 'hourly', 22 | 23 | // this is the auth source used for authentication, 24 | 'auth' => 'default-sp', 25 | // useridattr is the attribute-name that contains the userid as returned from idp 26 | 'useridattr' => 'uid', 27 | 28 | // You can create as many scopes as you want and assign attributes to them 29 | // WIP: Actually only basic scope is supported with all the attributes 30 | 'scopes' => [ 31 | 'basic' => [ 32 | 'icon' => 'user', 33 | 'description' => [ 34 | 'en' => 'Your username.', 35 | 'es' => 'Su nombre de usuario.' 36 | ], 37 | 'attributes' => ['uid'], 38 | ], 39 | ], 40 | ]; -------------------------------------------------------------------------------- /default-enable: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgomez/simplesamlphp-module-oauth2/1d4808f2b8e4d509af8d9db42b626169e67e8598/default-enable -------------------------------------------------------------------------------- /hooks/hook_cron.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | use SimpleSAML\Modules\OAuth2\Repositories\AccessTokenRepository; 12 | use SimpleSAML\Modules\OAuth2\Repositories\AuthCodeRepository; 13 | use SimpleSAML\Modules\OAuth2\Repositories\RefreshTokenRepository; 14 | 15 | function oauth2_hook_cron(&$croninfo) 16 | { 17 | assert('is_array($croninfo)'); 18 | assert('array_key_exists("summary", $croninfo)'); 19 | assert('array_key_exists("tag", $croninfo)'); 20 | 21 | $oauth2config = SimpleSAML_Configuration::getOptionalConfig('module_oauth2.php'); 22 | 23 | if (is_null($oauth2config->getValue('cron_tag', 'hourly'))) { 24 | return; 25 | } 26 | if ($oauth2config->getValue('cron_tag', null) !== $croninfo['tag']) { 27 | return; 28 | } 29 | 30 | try { 31 | $store = \SimpleSAML\Store::getInstance(); 32 | 33 | if (!$store instanceof \SimpleSAML\Modules\DBAL\Store\DBAL) { 34 | throw new \SimpleSAML_Error_Exception('OAuth2 module: Only DBAL Store is supported'); 35 | } 36 | 37 | $accessTokenRepository = new AccessTokenRepository(); 38 | $accessTokenRepository->removeExpiredAccessTokens(); 39 | 40 | $authTokenRepository = new AuthCodeRepository(); 41 | $authTokenRepository->removeExpiredAuthCodes(); 42 | 43 | $refreshTokenRepository = new RefreshTokenRepository(); 44 | $refreshTokenRepository->removeExpiredRefreshTokens(); 45 | 46 | $croninfo['summary'][] = 'OAuth2 clean up. Removed expired entries from OAuth2 storage.'; 47 | } catch (Exception $e) { 48 | $message = 'OAuth2 clean up cron script failed: '.$e->getMessage(); 49 | SimpleSAML\Logger::warning($message); 50 | $croninfo['summary'][] = $message; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /hooks/hook_dbal.php: -------------------------------------------------------------------------------- 1 | getPrefix().'_oauth2_user'; 14 | $user = $schema->createTable($userTable); 15 | $user->addColumn('id', 'string', ['length' => 255]); 16 | $user->addColumn('attributes', 'json_array', ['notnull' => false]); 17 | $user->addColumn('created_at', 'datetime'); 18 | $user->addColumn('updated_at', 'datetime'); 19 | $user->setPrimaryKey(['id']); 20 | 21 | $clientTable = $store->getPrefix().'_oauth2_client'; 22 | $client = $schema->createTable($clientTable); 23 | $client->addColumn('id', 'string', ['length' => 255]); 24 | $client->addColumn('secret', 'string', ['length' => 255]); 25 | $client->addColumn('name', 'string', ['length' => 255]); 26 | $client->addColumn('description', 'text', ['notnull' => false]); 27 | $client->addColumn('auth_source', 'string', ['length' => 255, 'notnull' => false]); 28 | $client->addColumn('redirect_uri', 'json_array'); 29 | $client->addColumn('scopes', 'json_array'); 30 | $client->setPrimaryKey(['id']); 31 | 32 | $accesstokenTable = $store->getPrefix().'_oauth2_accesstoken'; 33 | $accesstoken = $schema->createTable($accesstokenTable); 34 | $accesstoken->addColumn('id', 'string', ['length' => 255]); 35 | $accesstoken->addColumn('scopes', 'json_array', ['notnull' => false]); 36 | $accesstoken->addColumn('expires_at', 'datetime'); 37 | $accesstoken->addColumn('user_id', 'string', ['length' => 255]); 38 | $accesstoken->addColumn('client_id', 'string', ['length' => 255]); 39 | $accesstoken->addColumn('is_revoked', 'boolean', ['default' => false]); 40 | $accesstoken->setPrimaryKey(['id']); 41 | $accesstoken->addForeignKeyConstraint($clientTable, ['client_id'], ['id'], ['onDelete' => 'CASCADE']); 42 | $accesstoken->addForeignKeyConstraint($userTable, ['user_id'], ['id'], ['onDelete' => 'CASCADE']); 43 | 44 | $refreshtokenTable = $store->getPrefix().'_oauth2_refreshtoken'; 45 | $refreshtoken = $schema->createTable($refreshtokenTable); 46 | $refreshtoken->addColumn('id', 'string', ['length' => 255]); 47 | $refreshtoken->addColumn('expires_at', 'datetime'); 48 | $refreshtoken->addColumn('accesstoken_id', 'string', ['length' => 255]); 49 | $refreshtoken->addColumn('is_revoked', 'boolean', ['default' => false]); 50 | $refreshtoken->setPrimaryKey(['id']); 51 | $refreshtoken->addForeignKeyConstraint($accesstokenTable, ['accesstoken_id'], ['id'], ['onDelete' => 'CASCADE']); 52 | 53 | $authcodeTable = $store->getPrefix().'_oauth2_authcode'; 54 | $authcode = $schema->createTable($authcodeTable); 55 | $authcode->addColumn('id', 'string', ['length' => 255]); 56 | $authcode->addColumn('scopes', 'json_array'); 57 | $authcode->addColumn('expires_at', 'datetime'); 58 | $authcode->addColumn('user_id', 'string', ['length' => 255]); 59 | $authcode->addColumn('client_id', 'string', ['length' => 255]); 60 | $authcode->addColumn('is_revoked', 'boolean', ['default' => false]); 61 | $authcode->addColumn('redirect_uri', 'text'); 62 | $authcode->addForeignKeyConstraint($clientTable, ['client_id'], ['id'], ['onDelete' => 'CASCADE']); 63 | $authcode->addForeignKeyConstraint($userTable, ['user_id'], ['id'], ['onDelete' => 'CASCADE']); 64 | 65 | $store->createOrUpdateSchema($schema, $store->getPrefix().'_oauth2'); 66 | 67 | $dbinfo['summary'][] = 'Created OAuth2 Schema'; 68 | } 69 | -------------------------------------------------------------------------------- /hooks/hook_frontpage.php: -------------------------------------------------------------------------------- 1 | SimpleSAML\Module::getModuleURL('oauth2/registry.php'), 14 | 'text' => [ 15 | 'en' => 'OAuth2 Client Registry', 16 | 'es' => 'Registro de clientes OAuth2', 17 | ], 18 | 'shorttext' => [ 19 | 'en' => 'OAuth2 Registry', 20 | 'es' => 'Registro OAuth2', 21 | ], 22 | ]; 23 | } 24 | -------------------------------------------------------------------------------- /lib/SimpleSAML/Modules/OAuth2/Entity/AccessTokenEntity.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace SimpleSAML\Modules\OAuth2\Entity; 12 | 13 | use League\OAuth2\Server\Entities\AccessTokenEntityInterface; 14 | use League\OAuth2\Server\Entities\Traits\AccessTokenTrait; 15 | use League\OAuth2\Server\Entities\Traits\EntityTrait; 16 | use League\OAuth2\Server\Entities\Traits\TokenEntityTrait; 17 | 18 | class AccessTokenEntity implements AccessTokenEntityInterface 19 | { 20 | use AccessTokenTrait, TokenEntityTrait, EntityTrait; 21 | } 22 | -------------------------------------------------------------------------------- /lib/SimpleSAML/Modules/OAuth2/Entity/AuthCodeEntity.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace SimpleSAML\Modules\OAuth2\Entity; 12 | 13 | use League\OAuth2\Server\Entities\AuthCodeEntityInterface; 14 | use League\OAuth2\Server\Entities\Traits\AuthCodeTrait; 15 | use League\OAuth2\Server\Entities\Traits\EntityTrait; 16 | use League\OAuth2\Server\Entities\Traits\TokenEntityTrait; 17 | 18 | class AuthCodeEntity implements AuthCodeEntityInterface 19 | { 20 | use EntityTrait, TokenEntityTrait, AuthCodeTrait; 21 | } 22 | -------------------------------------------------------------------------------- /lib/SimpleSAML/Modules/OAuth2/Entity/ClientEntity.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace SimpleSAML\Modules\OAuth2\Entity; 12 | 13 | use League\OAuth2\Server\Entities\ClientEntityInterface; 14 | use League\OAuth2\Server\Entities\Traits\EntityTrait; 15 | 16 | class ClientEntity implements ClientEntityInterface 17 | { 18 | use EntityTrait; 19 | 20 | /** 21 | * @var string 22 | */ 23 | private $name; 24 | 25 | /** 26 | * @var string 27 | */ 28 | private $secret; 29 | 30 | /** 31 | * @var string 32 | */ 33 | private $redirectUri; 34 | 35 | /** 36 | * @var string 37 | */ 38 | private $authSource; 39 | 40 | /** 41 | * @return string 42 | */ 43 | public function getName() 44 | { 45 | return $this->name; 46 | } 47 | 48 | /** 49 | * @param string $name 50 | */ 51 | public function setName($name) 52 | { 53 | $this->name = $name; 54 | } 55 | 56 | /** 57 | * @return string 58 | */ 59 | public function getSecret() 60 | { 61 | return $this->secret; 62 | } 63 | 64 | /** 65 | * @param string $secret 66 | */ 67 | public function setSecret($secret) 68 | { 69 | $this->secret = $secret; 70 | } 71 | 72 | /** 73 | * @return string 74 | */ 75 | public function getRedirectUri() 76 | { 77 | return $this->redirectUri; 78 | } 79 | 80 | /** 81 | * @param string $redirectUri 82 | */ 83 | public function setRedirectUri($redirectUri) 84 | { 85 | $this->redirectUri = $redirectUri; 86 | } 87 | 88 | /** 89 | * @return string 90 | */ 91 | public function getAuthSource() 92 | { 93 | return $this->authSource; 94 | } 95 | 96 | /** 97 | * @param string $authSource 98 | */ 99 | public function setAuthSource($authSource) 100 | { 101 | $this->authSource = $authSource; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /lib/SimpleSAML/Modules/OAuth2/Entity/RefreshTokenEntity.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace SimpleSAML\Modules\OAuth2\Entity; 12 | 13 | use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; 14 | use League\OAuth2\Server\Entities\Traits\EntityTrait; 15 | use League\OAuth2\Server\Entities\Traits\RefreshTokenTrait; 16 | 17 | class RefreshTokenEntity implements RefreshTokenEntityInterface 18 | { 19 | use RefreshTokenTrait, EntityTrait; 20 | } 21 | -------------------------------------------------------------------------------- /lib/SimpleSAML/Modules/OAuth2/Entity/ScopeEntity.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace SimpleSAML\Modules\OAuth2\Entity; 12 | 13 | use League\OAuth2\Server\Entities\ScopeEntityInterface; 14 | use League\OAuth2\Server\Entities\Traits\EntityTrait; 15 | 16 | class ScopeEntity implements ScopeEntityInterface 17 | { 18 | use EntityTrait; 19 | 20 | /** 21 | * @var string 22 | */ 23 | private $icon; 24 | 25 | /** 26 | * @var string 27 | */ 28 | private $description; 29 | 30 | /** 31 | * @var array 32 | */ 33 | private $attributes; 34 | 35 | public function jsonSerialize() 36 | { 37 | return $this->getIdentifier(); 38 | } 39 | 40 | /** 41 | * @return string 42 | */ 43 | public function getIcon() 44 | { 45 | return $this->icon; 46 | } 47 | 48 | /** 49 | * @param string $icon 50 | */ 51 | public function setIcon($icon) 52 | { 53 | $this->icon = $icon; 54 | } 55 | 56 | /** 57 | * @return string 58 | */ 59 | public function getDescription() 60 | { 61 | return $this->description; 62 | } 63 | 64 | /** 65 | * @param string $description 66 | */ 67 | public function setDescription($description) 68 | { 69 | $this->description = $description; 70 | } 71 | 72 | /** 73 | * @return array 74 | */ 75 | public function getAttributes() 76 | { 77 | return $this->attributes; 78 | } 79 | 80 | /** 81 | * @param array $attributes 82 | */ 83 | public function setAttributes($attributes) 84 | { 85 | $this->attributes = $attributes; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /lib/SimpleSAML/Modules/OAuth2/Entity/UserEntity.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace SimpleSAML\Modules\OAuth2\Entity; 12 | 13 | use League\OAuth2\Server\Entities\UserEntityInterface; 14 | 15 | class UserEntity implements UserEntityInterface 16 | { 17 | /** 18 | * @var string 19 | */ 20 | private $identifier; 21 | 22 | /** 23 | * @var array 24 | */ 25 | private $attributes; 26 | 27 | /** 28 | * @var \DateTime 29 | */ 30 | private $createdAt; 31 | 32 | /** 33 | * @var \DateTime 34 | */ 35 | private $updatedAt; 36 | 37 | /** 38 | * {@inheritdoc} 39 | */ 40 | public function __construct($identifier) 41 | { 42 | $this->identifier = $identifier; 43 | $this->createdAt = new \DateTime(); 44 | $this->updatedAt = $this->createdAt; 45 | } 46 | 47 | /** 48 | * @return mixed 49 | */ 50 | public function getIdentifier() 51 | { 52 | return $this->identifier; 53 | } 54 | 55 | /** 56 | * @param mixed $identifier 57 | */ 58 | public function setIdentifier($identifier) 59 | { 60 | $this->identifier = $identifier; 61 | } 62 | 63 | /** 64 | * @return array 65 | */ 66 | public function getAttributes() 67 | { 68 | return $this->attributes; 69 | } 70 | 71 | /** 72 | * @param array $attributes 73 | */ 74 | public function setAttributes($attributes) 75 | { 76 | $this->attributes = $attributes; 77 | } 78 | 79 | /** 80 | * @return \DateTime 81 | */ 82 | public function getCreatedAt() 83 | { 84 | return $this->createdAt; 85 | } 86 | 87 | /** 88 | * @param \DateTime $createdAt 89 | */ 90 | public function setCreatedAt($createdAt) 91 | { 92 | $this->createdAt = $createdAt; 93 | } 94 | 95 | /** 96 | * @return \DateTime 97 | */ 98 | public function getUpdatedAt() 99 | { 100 | return $this->updatedAt; 101 | } 102 | 103 | /** 104 | * @param \DateTime $updatedAt 105 | */ 106 | public function setUpdatedAt($updatedAt) 107 | { 108 | $this->updatedAt = $updatedAt; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /lib/SimpleSAML/Modules/OAuth2/Form/ClientForm.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace SimpleSAML\Modules\OAuth2\Form; 12 | 13 | use Nette\Forms\Form; 14 | use SimpleSAML\Module; 15 | 16 | class ClientForm extends Form 17 | { 18 | /** 19 | * {@inheritdoc} 20 | */ 21 | public function __construct($name) 22 | { 23 | parent::__construct($name); 24 | 25 | $this->onValidate[] = [$this, 'validateRedirectUri']; 26 | 27 | $this->setMethod('POST'); 28 | $this->addProtection('Security token has expired, please submit the form again'); 29 | 30 | $this->addText('name', 'Name of client:') 31 | ->setMaxLength(255) 32 | ->setRequired('Set a name') 33 | ; 34 | $this->addTextArea('description', 'Description of client:', null, 5); 35 | $this->addTextArea('redirect_uri', 'Static/enforcing callback-url (one per line)', null, 5) 36 | ->setRequired('Write one redirect URI at least') 37 | ; 38 | $this->addSelect('auth_source', 'Authorization source:') 39 | ->setItems(\SimpleSAML_Auth_Source::getSources(), false) 40 | ->setPrompt('Pick an AuthSource or blank for default') 41 | ->setRequired(false) 42 | ; 43 | 44 | $this->addSubmit('submit', 'Submit'); 45 | $this->addButton('return', 'Return') 46 | ->setAttribute('onClick', 'parent.location = \''.Module::getModuleURL('oauth2/registry.php').'\'') 47 | ; 48 | } 49 | 50 | public function validateRedirectUri($form) 51 | { 52 | $values = $this->getValues(); 53 | $redirect_uris = $values['redirect_uri']; 54 | foreach ($redirect_uris as $redirect_uri) { 55 | if (false === filter_var($redirect_uri, FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED)) { 56 | $this->addError('Invalid URI: '.$redirect_uri); 57 | } 58 | } 59 | } 60 | 61 | /** 62 | * {@inheritdoc} 63 | */ 64 | public function getValues($asArray = false) 65 | { 66 | $values = parent::getValues(true); 67 | 68 | // Sanitize Redirect URIs 69 | $redirect_uris = preg_split("/[\t\r\n]+/", $values['redirect_uri']); 70 | $redirect_uris = array_filter($redirect_uris, function ($redirect_uri) { 71 | return !empty(trim($redirect_uri)); 72 | }); 73 | $values['redirect_uri'] = $redirect_uris; 74 | 75 | return $values; 76 | } 77 | 78 | /** 79 | * {@inheritdoc} 80 | */ 81 | public function setDefaults($values, $erase = false) 82 | { 83 | $values['redirect_uri'] = implode("\n", $values['redirect_uri']); 84 | 85 | return parent::setDefaults($values, $erase); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /lib/SimpleSAML/Modules/OAuth2/OAuth2AuthorizationServer.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace SimpleSAML\Modules\OAuth2; 12 | 13 | use League\OAuth2\Server\AuthorizationServer; 14 | use League\OAuth2\Server\CryptKey; 15 | use League\OAuth2\Server\Grant\AuthCodeGrant; 16 | use League\OAuth2\Server\Grant\ImplicitGrant; 17 | use League\OAuth2\Server\Grant\RefreshTokenGrant; 18 | use SimpleSAML\Modules\OAuth2\Repositories\AccessTokenRepository; 19 | use SimpleSAML\Modules\OAuth2\Repositories\AuthCodeRepository; 20 | use SimpleSAML\Modules\OAuth2\Repositories\ClientRepository; 21 | use SimpleSAML\Modules\OAuth2\Repositories\RefreshTokenRepository; 22 | use SimpleSAML\Modules\OAuth2\Repositories\ScopeRepository; 23 | use SimpleSAML\Utils\Config; 24 | 25 | class OAuth2AuthorizationServer 26 | { 27 | private static $instance; 28 | 29 | public static function getInstance() 30 | { 31 | if (self::$instance !== null) { 32 | return self::$instance; 33 | } 34 | 35 | $oauth2config = \SimpleSAML_Configuration::getConfig('module_oauth2.php'); 36 | $accessTokenDuration = $oauth2config->getString('accessTokenDuration'); 37 | $authCodeDuration = $oauth2config->getString('authCodeDuration'); 38 | $passPhrase = $oauth2config->getString('pass_phrase', null); 39 | $refreshTokenDuration = $oauth2config->getString('refreshTokenDuration'); 40 | 41 | $privateKeyPath = Config::getCertPath('oauth2_module.pem'); 42 | $privateKey = new CryptKey($privateKeyPath, $passPhrase); 43 | $encryptionKey = Config::getSecretSalt(); 44 | 45 | self::$instance = new AuthorizationServer( 46 | new ClientRepository(), 47 | new AccessTokenRepository(), 48 | new ScopeRepository(), 49 | $privateKey, 50 | $encryptionKey 51 | ); 52 | 53 | $refreshTokenRepository = new RefreshTokenRepository(); 54 | 55 | $authCodeGrant = new AuthCodeGrant( 56 | new AuthCodeRepository(), 57 | $refreshTokenRepository, 58 | new \DateInterval($authCodeDuration) 59 | ); 60 | $authCodeGrant->setRefreshTokenTTL(new \DateInterval($refreshTokenDuration)); // refresh tokens will expire after 1 month 61 | 62 | self::$instance->enableGrantType( 63 | $authCodeGrant, 64 | new \DateInterval($accessTokenDuration) 65 | ); 66 | 67 | $implicitGrant = new ImplicitGrant(new \DateInterval($accessTokenDuration)); 68 | 69 | self::$instance->enableGrantType( 70 | $implicitGrant, 71 | new \DateInterval($accessTokenDuration) 72 | ); 73 | 74 | $refreshTokenGrant = new RefreshTokenGrant($refreshTokenRepository); 75 | $refreshTokenGrant->setRefreshTokenTTL(new \DateInterval($refreshTokenDuration)); 76 | 77 | self::$instance->enableGrantType( 78 | $refreshTokenGrant, 79 | new \DateInterval($authCodeDuration) 80 | ); 81 | 82 | return self::$instance; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /lib/SimpleSAML/Modules/OAuth2/OAuth2ResourceServer.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace SimpleSAML\Modules\OAuth2; 12 | 13 | use League\OAuth2\Server\CryptKey; 14 | use League\OAuth2\Server\ResourceServer; 15 | use SimpleSAML\Modules\OAuth2\Repositories\AccessTokenRepository; 16 | use SimpleSAML\Utils\Config; 17 | 18 | class OAuth2ResourceServer 19 | { 20 | private static $instance; 21 | 22 | public static function getInstance() 23 | { 24 | if (self::$instance !== null) { 25 | return self::$instance; 26 | } 27 | 28 | $publicKeyPath = Config::getCertPath('oauth2_module.crt'); 29 | $publicKey = new CryptKey($publicKeyPath); 30 | 31 | self::$instance = new ResourceServer( 32 | new AccessTokenRepository(), 33 | $publicKey 34 | ); 35 | 36 | return self::$instance; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/SimpleSAML/Modules/OAuth2/Repositories/AbstractDBALRepository.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace SimpleSAML\Modules\OAuth2\Repositories; 12 | 13 | use Doctrine\DBAL\Connection; 14 | use SimpleSAML\Modules\DBAL\Store\DBAL; 15 | 16 | abstract class AbstractDBALRepository 17 | { 18 | /** 19 | * @var DBAL 20 | */ 21 | protected $store; 22 | 23 | /** 24 | * @var Connection 25 | */ 26 | protected $conn; 27 | 28 | /** 29 | * @var \SimpleSAML_Configuration 30 | */ 31 | protected $config; 32 | 33 | /** 34 | * ClientRepository constructor. 35 | */ 36 | public function __construct() 37 | { 38 | $this->config = \SimpleSAML_Configuration::getOptionalConfig('module_oauth2.php'); 39 | $this->store = \SimpleSAML\Store::getInstance(); 40 | 41 | if (!$this->store instanceof DBAL) { 42 | throw new \SimpleSAML_Error_Exception('OAuth2 module: Only DBAL Store is supported'); 43 | } 44 | 45 | $this->conn = $this->store->getConnection(); 46 | } 47 | 48 | abstract public function getTableName(); 49 | } 50 | -------------------------------------------------------------------------------- /lib/SimpleSAML/Modules/OAuth2/Repositories/AccessTokenRepository.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace SimpleSAML\Modules\OAuth2\Repositories; 12 | 13 | use League\OAuth2\Server\Entities\AccessTokenEntityInterface; 14 | use League\OAuth2\Server\Entities\ClientEntityInterface; 15 | use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; 16 | use SimpleSAML\Modules\OAuth2\Entity\AccessTokenEntity; 17 | 18 | class AccessTokenRepository extends AbstractDBALRepository implements AccessTokenRepositoryInterface 19 | { 20 | /** 21 | * {@inheritdoc} 22 | */ 23 | public function getNewToken(ClientEntityInterface $clientEntity, array $scopes, $userIdentifier = null) 24 | { 25 | $accessToken = new AccessTokenEntity(); 26 | $accessToken->setClient($clientEntity); 27 | foreach ($scopes as $scope) { 28 | $accessToken->addScope($scope); 29 | } 30 | 31 | return $accessToken; 32 | } 33 | 34 | /** 35 | * {@inheritdoc} 36 | */ 37 | public function persistNewAccessToken(AccessTokenEntityInterface $accessTokenEntity) 38 | { 39 | $scopes = []; 40 | foreach ($accessTokenEntity->getScopes() as $scope) { 41 | $scopes[] = $scope->getIdentifier(); 42 | } 43 | 44 | $this->conn->insert( 45 | $this->getTableName(), 46 | [ 47 | 'id' => $accessTokenEntity->getIdentifier(), 48 | 'scopes' => $scopes, 49 | 'expires_at' => $accessTokenEntity->getExpiryDateTime(), 50 | 'user_id' => $accessTokenEntity->getUserIdentifier(), 51 | 'client_id' => $accessTokenEntity->getClient()->getIdentifier(), 52 | ], [ 53 | 'string', 54 | 'json_array', 55 | 'datetime', 56 | 'string', 57 | 'string', 58 | ] 59 | ); 60 | } 61 | 62 | public function getUserId($tokenId) 63 | { 64 | $userId = $this->conn->fetchColumn( 65 | 'SELECT user_id FROM '.$this->getTableName().' WHERE id = ?', 66 | [$tokenId] 67 | ); 68 | 69 | return $this->conn->convertToPHPValue($userId, 'string'); 70 | } 71 | 72 | /** 73 | * {@inheritdoc} 74 | */ 75 | public function revokeAccessToken($tokenId) 76 | { 77 | $this->conn->update($this->getTableName(), ['is_revoked' => true], ['id' => $tokenId]); 78 | } 79 | 80 | /** 81 | * {@inheritdoc} 82 | */ 83 | public function isAccessTokenRevoked($tokenId) 84 | { 85 | return $this->conn->fetchColumn( 86 | 'SELECT is_revoked FROM '.$this->getTableName().' WHERE id = ?', 87 | [$tokenId] 88 | ); 89 | } 90 | 91 | public function removeExpiredAccessTokens() 92 | { 93 | $this->conn->executeUpdate( 94 | 'DELETE FROM '.$this->getTableName().' WHERE expires_at < ?', 95 | [new \DateTime()], 96 | ['datetime'] 97 | ); 98 | } 99 | 100 | public function getTableName() 101 | { 102 | return $this->store->getPrefix().'_oauth2_accesstoken'; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /lib/SimpleSAML/Modules/OAuth2/Repositories/AuthCodeRepository.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace SimpleSAML\Modules\OAuth2\Repositories; 12 | 13 | use League\OAuth2\Server\Entities\AuthCodeEntityInterface; 14 | use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface; 15 | use SimpleSAML\Modules\OAuth2\Entity\AuthCodeEntity; 16 | 17 | class AuthCodeRepository extends AbstractDBALRepository implements AuthCodeRepositoryInterface 18 | { 19 | /** 20 | * {@inheritdoc} 21 | */ 22 | public function getNewAuthCode() 23 | { 24 | return new AuthCodeEntity(); 25 | } 26 | 27 | /** 28 | * {@inheritdoc} 29 | */ 30 | public function persistNewAuthCode(AuthCodeEntityInterface $authCodeEntity) 31 | { 32 | $scopes = []; 33 | foreach ($authCodeEntity->getScopes() as $scope) { 34 | $scopes[] = $scope->getIdentifier(); 35 | } 36 | 37 | $this->conn->insert($this->getTableName(), [ 38 | 'id' => $authCodeEntity->getIdentifier(), 39 | 'scopes' => $scopes, 40 | 'expires_at' => $authCodeEntity->getExpiryDateTime(), 41 | 'user_id' => $authCodeEntity->getUserIdentifier(), 42 | 'client_id' => $authCodeEntity->getClient()->getIdentifier(), 43 | 'redirect_uri' => $authCodeEntity->getRedirectUri(), 44 | ], [ 45 | 'string', 46 | 'json_array', 47 | 'datetime', 48 | 'string', 49 | 'string', 50 | 'string', 51 | ]); 52 | } 53 | 54 | /** 55 | * {@inheritdoc} 56 | */ 57 | public function revokeAuthCode($codeId) 58 | { 59 | $this->conn->update($this->getTableName(), ['is_revoked' => true], ['id' => $codeId]); 60 | } 61 | 62 | /** 63 | * {@inheritdoc} 64 | */ 65 | public function isAuthCodeRevoked($codeId) 66 | { 67 | return $this->conn->fetchColumn('SELECT is_revoked FROM '.$this->getTableName().' WHERE id = ?', [$codeId]); 68 | } 69 | 70 | public function removeExpiredAuthCodes() 71 | { 72 | $this->conn->executeUpdate(' 73 | DELETE FROM '.$this->getTableName().' 74 | WHERE expires_at < ? 75 | ', 76 | [ 77 | new \DateTime(), 78 | ], 79 | [ 80 | 'datetime', 81 | ] 82 | ); 83 | } 84 | 85 | public function getTableName() 86 | { 87 | return $this->store->getPrefix().'_oauth2_authcode'; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /lib/SimpleSAML/Modules/OAuth2/Repositories/ClientRepository.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace SimpleSAML\Modules\OAuth2\Repositories; 12 | 13 | use League\OAuth2\Server\Repositories\ClientRepositoryInterface; 14 | use SimpleSAML\Modules\OAuth2\Entity\ClientEntity; 15 | use SimpleSAML\Utils\Random; 16 | 17 | class ClientRepository extends AbstractDBALRepository implements ClientRepositoryInterface 18 | { 19 | /** 20 | * {@inheritdoc} 21 | */ 22 | public function getClientEntity($clientIdentifier, $grantType, $clientSecret = null, $mustValidateSecret = true) 23 | { 24 | /** @var \SimpleSAML\Modules\OAuth2\Entity\ClientEntity $entity */ 25 | $entity = $this->find($clientIdentifier); 26 | 27 | if (!$entity) { 28 | return null; 29 | } 30 | 31 | if ($clientSecret && $clientSecret !== $entity['secret']) { 32 | return null; 33 | } 34 | 35 | $client = new ClientEntity(); 36 | $client->setIdentifier($clientIdentifier); 37 | $client->setName($entity['name']); 38 | $client->setRedirectUri($entity['redirect_uri']); 39 | $client->setSecret($entity['secret']); 40 | $client->setAuthSource($entity['auth_source']); 41 | 42 | return $client; 43 | } 44 | 45 | public function persistNewClient($id, $secret, $name, $description, $authSource, $redirectUri) 46 | { 47 | if (false === is_array($redirectUri)) { 48 | if (is_string($redirectUri)) { 49 | $redirectUri = [$redirectUri]; 50 | } else { 51 | throw new \InvalidArgumentException('Client redirect URI must be a string or an array.'); 52 | } 53 | } 54 | 55 | $this->conn->insert($this->getTableName(), [ 56 | 'id' => $id, 57 | 'secret' => $secret, 58 | 'name' => $name, 59 | 'description' => $description, 60 | 'auth_source' => $authSource, 61 | 'redirect_uri' => $redirectUri, 62 | 'scopes' => ['basic'], 63 | ], [ 64 | 'string', 65 | 'string', 66 | 'string', 67 | 'string', 68 | 'string', 69 | 'json_array', 70 | 'json_array', 71 | ]); 72 | } 73 | 74 | public function updateClient($id, $name, $description, $authSource, $redirectUri) 75 | { 76 | $this->conn->update($this->getTableName(), [ 77 | 'name' => $name, 78 | 'description' => $description, 79 | 'auth_source' => $authSource, 80 | 'redirect_uri' => $redirectUri, 81 | 'scopes' => ['basic'], 82 | ], [ 83 | 'id' => $id, 84 | ], [ 85 | 'string', 86 | 'string', 87 | 'string', 88 | 'json_array', 89 | 'json_array', 90 | ]); 91 | } 92 | 93 | public function delete($clientIdentifier) 94 | { 95 | $conn = $this->store->getConnection(); 96 | $conn->delete($this->getTableName(), [ 97 | 'id' => $clientIdentifier, 98 | ]); 99 | } 100 | 101 | /** 102 | * @param $clientIdentifier 103 | * 104 | * @return array 105 | */ 106 | public function find($clientIdentifier) 107 | { 108 | $client = $this->conn->fetchAssoc( 109 | 'SELECT * FROM '.$this->getTableName().' WHERE id = ?', 110 | [ 111 | $clientIdentifier, 112 | ], [ 113 | 'string', 114 | ] 115 | ); 116 | 117 | if ($client) { 118 | $client['redirect_uri'] = $this->conn->convertToPHPValue($client['redirect_uri'], 'json_array'); 119 | $client['scopes'] = $this->conn->convertToPHPValue($client['scopes'], 'json_array'); 120 | } 121 | 122 | return $client; 123 | } 124 | 125 | /** 126 | * @return ClientEntity[] 127 | */ 128 | public function findAll() 129 | { 130 | $clients = $this->conn->fetchAll( 131 | 'SELECT * FROM '.$this->getTableName() 132 | ); 133 | 134 | foreach ($clients as &$client) { 135 | $client['redirect_uri'] = $this->conn->convertToPHPValue($client['redirect_uri'], 'json_array'); 136 | $client['scopes'] = $this->conn->convertToPHPValue($client['scopes'], 'json_array'); 137 | } 138 | 139 | return $clients; 140 | } 141 | 142 | public function getTableName() 143 | { 144 | return $this->store->getPrefix().'_oauth2_client'; 145 | } 146 | 147 | public function restoreSecret($clientIdentifier) 148 | { 149 | $secret = Random::generateID(); 150 | $this->conn->update($this->getTableName(), [ 151 | 'secret' => $secret, 152 | ], [ 153 | 'id' => $clientIdentifier, 154 | ], [ 155 | 'string', 156 | ]); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /lib/SimpleSAML/Modules/OAuth2/Repositories/RefreshTokenRepository.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace SimpleSAML\Modules\OAuth2\Repositories; 12 | 13 | use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; 14 | use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; 15 | use SimpleSAML\Modules\OAuth2\Entity\RefreshTokenEntity; 16 | 17 | class RefreshTokenRepository extends AbstractDBALRepository implements RefreshTokenRepositoryInterface 18 | { 19 | /** 20 | * {@inheritdoc} 21 | */ 22 | public function getNewRefreshToken() 23 | { 24 | return new RefreshTokenEntity(); 25 | } 26 | 27 | /** 28 | * {@inheritdoc} 29 | */ 30 | public function persistNewRefreshToken(RefreshTokenEntityInterface $refreshTokenEntity) 31 | { 32 | $this->conn->insert($this->getTableName(), [ 33 | 'id' => $refreshTokenEntity->getIdentifier(), 34 | 'expires_at' => $refreshTokenEntity->getExpiryDateTime(), 35 | 'accesstoken_id' => $refreshTokenEntity->getAccessToken()->getIdentifier(), 36 | ], [ 37 | 'string', 38 | 'datetime', 39 | 'string', 40 | ]); 41 | } 42 | 43 | /** 44 | * {@inheritdoc} 45 | */ 46 | public function revokeRefreshToken($tokenId) 47 | { 48 | $this->conn->update($this->getTableName(), ['is_revoked' => true], ['id' => $tokenId]); 49 | } 50 | 51 | /** 52 | * {@inheritdoc} 53 | */ 54 | public function isRefreshTokenRevoked($tokenId) 55 | { 56 | return $this->conn->fetchColumn('SELECT is_revoked FROM '.$this->getTableName().' WHERE id = ?', [$tokenId]); 57 | } 58 | 59 | public function removeExpiredRefreshTokens() 60 | { 61 | $this->conn->executeUpdate(' 62 | DELETE FROM '.$this->getTableName().' 63 | WHERE expires_at < ? 64 | ', 65 | [ 66 | new \DateTime(), 67 | ], 68 | [ 69 | 'datetime', 70 | ] 71 | ); 72 | } 73 | 74 | public function getTableName() 75 | { 76 | return $this->store->getPrefix().'_oauth2_refreshtoken'; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /lib/SimpleSAML/Modules/OAuth2/Repositories/ScopeRepository.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace SimpleSAML\Modules\OAuth2\Repositories; 12 | 13 | use League\OAuth2\Server\Entities\ClientEntityInterface; 14 | use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; 15 | use SimpleSAML\Modules\OAuth2\Entity\ScopeEntity; 16 | 17 | class ScopeRepository implements ScopeRepositoryInterface 18 | { 19 | /** 20 | * {@inheritdoc} 21 | */ 22 | public function getScopeEntityByIdentifier($identifier) 23 | { 24 | $oauth2config = \SimpleSAML_Configuration::getConfig('module_oauth2.php'); 25 | 26 | $scopes = $oauth2config->getArray('scopes'); 27 | 28 | if (array_key_exists($identifier, $scopes) === false) { 29 | return null; 30 | } 31 | 32 | $scope = new ScopeEntity(); 33 | $scope->setIdentifier($identifier); 34 | $scope->setIcon($scopes[$identifier]['icon']); 35 | $scope->setDescription($scopes[$identifier]['description']); 36 | $scope->setAttributes($scopes[$identifier]['attributes']); 37 | 38 | return $scope; 39 | } 40 | 41 | /** 42 | * {@inheritdoc} 43 | */ 44 | public function finalizeScopes( 45 | array $scopes, 46 | $grantType, 47 | ClientEntityInterface $clientEntity, 48 | $userIdentifier = null 49 | ) { 50 | return $scopes; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/SimpleSAML/Modules/OAuth2/Repositories/UserRepository.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace SimpleSAML\Modules\OAuth2\Repositories; 12 | 13 | use League\OAuth2\Server\Entities\ClientEntityInterface; 14 | use League\OAuth2\Server\Repositories\UserRepositoryInterface; 15 | 16 | class UserRepository extends AbstractDBALRepository implements UserRepositoryInterface 17 | { 18 | public function getUserEntityByUserCredentials( 19 | $username, 20 | $password, 21 | $grantType, 22 | ClientEntityInterface $clientEntity 23 | ) { 24 | throw new \Exception('Not supported'); 25 | } 26 | 27 | public function persistNewUser($id, $attributes) 28 | { 29 | $now = new \DateTime(); 30 | 31 | $this->conn->insert($this->getTableName(), 32 | [ 33 | 'id' => $id, 34 | 'attributes' => $attributes, 35 | 'created_at' => $now, 36 | 'updated_at' => $now, 37 | ], [ 38 | 'string', 39 | 'json_array', 40 | 'datetime', 41 | 'datetime', 42 | ] 43 | ); 44 | } 45 | 46 | public function updateUser($id, $attributes) 47 | { 48 | $now = new \DateTime(); 49 | 50 | return $this->conn->update($this->getTableName(), 51 | [ 52 | 'attributes' => $attributes, 53 | 'updated_at' => $now, 54 | ], [ 55 | 'id' => $id, 56 | ], [ 57 | 'json_array', 58 | 'datetime', 59 | ] 60 | ); 61 | } 62 | 63 | public function delete($userIdentifier) 64 | { 65 | $this->conn->delete($this->getTableName(), [ 66 | 'id' => $userIdentifier, 67 | ]); 68 | } 69 | 70 | public function insertOrCreate($userId, $attributes) 71 | { 72 | if (0 === $this->updateUser($userId, $attributes)) { 73 | $this->persistNewUser($userId, $attributes); 74 | } 75 | } 76 | 77 | public function getAttributes($userId) 78 | { 79 | $attributes = $this->conn->fetchColumn( 80 | 'SELECT attributes FROM '.$this->getTableName().' WHERE id = ?', 81 | [$userId] 82 | ); 83 | 84 | return $this->conn->convertToPHPValue($attributes, 'json_array'); 85 | } 86 | 87 | public function getTableName() 88 | { 89 | return $this->store->getPrefix().'_oauth2_user'; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /templates/registry_edit.twig: -------------------------------------------------------------------------------- 1 | {% extends "base.twig" %} 2 | 3 | {% set moduleurlpath = '/' ~ baseurlpath ~ 'module.php/oauth2/' %} 4 | 5 | {% block preload %} 6 | 7 | {% endblock %} 8 | 9 | {% block content %} 10 |

Oauth2 Client Registry

11 |

Here you can edit an OAuth2 client.

12 | 13 | {{ form | raw }} 14 | {% endblock %} 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /templates/registry_list.twig: -------------------------------------------------------------------------------- 1 | {% extends "base.twig" %} 2 | 3 | {% set moduleurlpath = '/' ~ baseurlpath ~ 'module.php/oauth2/' %} 4 | 5 | {% block preload %} 6 | 7 | {% endblock %} 8 | 9 | {% block content %} 10 |

Oauth2 Client Registry

11 |

Here you can register new OAuth2 Clients.

12 | 13 |

Your clients

14 | 15 | {% for client in clients %} 16 | 17 | 18 | 20 | 21 | 22 | 24 | 25 | 26 | 28 | 29 | 30 | 32 | 33 | 34 | 36 | 37 | 38 | 46 | 47 | 48 | 53 | 54 |
Name 19 | {{ client.name }}
Description 23 | {{ client.description }}
Client ID 27 | {{ client.id }}
Client ID 31 | {{ client.auth_source }}
Client Secret 35 | {{ client.secret }}
Redirect URIs 39 | 40 |
    41 | {% for uri in client.redirect_uri %} 42 |
  • {{ uri }}
  • 43 | {% endfor %} 44 |
45 |
49 | Edit config 50 | Restore secret 51 | Delete 52 |
55 |
56 | {% else %} 57 | 58 | 59 | 60 | 61 |
No clients registered
62 | {% endfor %} 63 | 64 | Add new client 65 | {% endblock %} 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /templates/registry_new.twig: -------------------------------------------------------------------------------- 1 | {% extends "base.twig" %} 2 | 3 | {% block preload %} 4 | 5 | {% endblock %} 6 | 7 | {% block content %} 8 |

Oauth2 Client Registry

9 |

Here you can register a new OAuth2 client.

10 | 11 | {{ form | raw }} 12 | {% endblock %} 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /www/access_token.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | use SimpleSAML\Modules\OAuth2\OAuth2AuthorizationServer; 12 | use Zend\Diactoros\Response; 13 | use Zend\Diactoros\ServerRequestFactory; 14 | 15 | try { 16 | $server = OAuth2AuthorizationServer::getInstance(); 17 | $request = ServerRequestFactory::fromGlobals(); 18 | 19 | $response = $server->respondToAccessTokenRequest($request, new Response()); 20 | 21 | $emiter = new Response\SapiEmitter(); 22 | $emiter->emit($response); 23 | } catch (Exception $e) { 24 | header('Content-type: text/plain; utf-8', true, 500); 25 | header('OAuth-Error: '.$e->getMessage()); 26 | 27 | print_r($e); 28 | } 29 | -------------------------------------------------------------------------------- /www/authorize.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | use SimpleSAML\Modules\OAuth2\Entity\UserEntity; 12 | use SimpleSAML\Modules\OAuth2\OAuth2AuthorizationServer; 13 | use SimpleSAML\Modules\OAuth2\Repositories\ClientRepository; 14 | use SimpleSAML\Modules\OAuth2\Repositories\UserRepository; 15 | use Zend\Diactoros\Response; 16 | use Zend\Diactoros\ServerRequestFactory; 17 | 18 | try { 19 | $request = ServerRequestFactory::fromGlobals(); 20 | $parameters = $request->getQueryParams(); 21 | $clientId = array_key_exists('client_id', $parameters) ? $parameters['client_id'] : null; 22 | 23 | // The AS could be configured by client 24 | $clientRepository = new ClientRepository(); 25 | $client = $clientRepository->find($clientId); 26 | 27 | $oauth2config = \SimpleSAML_Configuration::getOptionalConfig('module_oauth2.php'); 28 | 29 | if (!$client || !$client['auth_source']) { 30 | $as = $oauth2config->getString('auth'); 31 | } else { 32 | $as = $client['auth_source']; 33 | } 34 | 35 | $auth = new \SimpleSAML_Auth_Simple($as); 36 | $auth->requireAuth(); 37 | 38 | $attributes = $auth->getAttributes(); 39 | $useridattr = $oauth2config->getString('useridattr'); 40 | 41 | if (!isset($attributes[$useridattr])) { 42 | throw new \Exception('Oauth2 useridattr doesn\'t exists. Available attributes are: '.implode(', ', $attributes)); 43 | } 44 | $userid = $attributes[$useridattr][0]; 45 | 46 | // Persists the user attributes on the database 47 | $userRepository = new UserRepository(); 48 | $userRepository->insertOrCreate($userid, $attributes); 49 | 50 | $server = OAuth2AuthorizationServer::getInstance(); 51 | $authRequest = $server->validateAuthorizationRequest($request); 52 | $authRequest->setUser(new UserEntity($userid)); 53 | $authRequest->setAuthorizationApproved(true); 54 | 55 | $response = $server->completeAuthorizationRequest($authRequest, new Response()); 56 | 57 | $emiter = new Response\SapiEmitter(); 58 | $emiter->emit($response); 59 | } catch (Exception $e) { 60 | header('Content-type: text/plain; utf-8', true, 500); 61 | header('OAuth-Error: '.$e->getMessage()); 62 | 63 | print_r($e); 64 | } 65 | -------------------------------------------------------------------------------- /www/registry.edit.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | use SimpleSAML\Modules\OAuth2\Form\ClientForm; 12 | use SimpleSAML\Modules\OAuth2\Repositories\ClientRepository; 13 | use SimpleSAML\Utils\Auth; 14 | use SimpleSAML\Utils\HTTP; 15 | 16 | Auth::requireAdmin(); 17 | 18 | /* Load simpleSAMLphp, configuration and metadata */ 19 | $client_id = $_REQUEST['id']; 20 | $action = \SimpleSAML\Module::getModuleURL('oauth2/registry.edit.php', ['id' => $client_id]); 21 | $config = \SimpleSAML_Configuration::getInstance(); 22 | 23 | $clientRepository = new ClientRepository(); 24 | $client = $clientRepository->find($client_id); 25 | if (!$client) { 26 | header('Content-type: text/plain; utf-8', true, 500); 27 | 28 | echo 'Client not found'; 29 | 30 | return; 31 | } 32 | 33 | $form = new ClientForm('client'); 34 | $form->setAction($action); 35 | $form->setDefaults($client); 36 | 37 | if ($form->isSubmitted() && $form->isSuccess()) { 38 | $client = $form->getValues(); 39 | 40 | $clientRepository->updateClient( 41 | $client_id, 42 | $client['name'], 43 | $client['description'], 44 | $client['auth_source'], 45 | $client['redirect_uri'] 46 | ); 47 | 48 | HTTP::redirectTrustedURL('registry.php'); 49 | } 50 | 51 | $template = new \SimpleSAML_XHTML_Template($config, 'oauth2:registry_edit'); 52 | $template->data['form'] = $form; 53 | $template->show(); 54 | -------------------------------------------------------------------------------- /www/registry.new.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | use SimpleSAML\Modules\OAuth2\Form\ClientForm; 12 | use SimpleSAML\Modules\OAuth2\Repositories\ClientRepository; 13 | use SimpleSAML\Utils\Auth; 14 | use SimpleSAML\Utils\HTTP; 15 | use SimpleSAML\Utils\Random; 16 | 17 | /* Load simpleSAMLphp, configuration and metadata */ 18 | $action = \SimpleSAML\Module::getModuleURL('oauth2/registry.new.php'); 19 | $config = \SimpleSAML_Configuration::getInstance(); 20 | 21 | Auth::requireAdmin(); 22 | 23 | $form = new ClientForm('client'); 24 | $form->setAction($action); 25 | 26 | if ($form->isSubmitted() && $form->isSuccess()) { 27 | $client = $form->getValues(); 28 | $client['id'] = Random::generateID(); 29 | $client['secret'] = Random::generateID(); 30 | 31 | $clientRepository = new ClientRepository(); 32 | $clientRepository->persistNewClient( 33 | $client['id'], 34 | $client['secret'], 35 | $client['name'], 36 | $client['description'], 37 | $client['auth_source'], 38 | $client['redirect_uri'] 39 | ); 40 | 41 | HTTP::redirectTrustedURL('registry.php'); 42 | } 43 | 44 | $template = new \SimpleSAML_XHTML_Template($config, 'oauth2:registry_new'); 45 | $template->data['form'] = $form; 46 | $template->show(); 47 | -------------------------------------------------------------------------------- /www/registry.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | use SimpleSAML\Modules\OAuth2\Repositories\ClientRepository; 12 | use SimpleSAML\Utils\Auth; 13 | use SimpleSAML\Utils\HTTP; 14 | 15 | $config = \SimpleSAML_Configuration::getInstance(); 16 | $session = \SimpleSAML_Session::getSessionFromRequest(); 17 | $oauthconfig = \SimpleSAML_Configuration::getOptionalConfig('module_oauth2.php'); 18 | 19 | Auth::requireAdmin(); 20 | 21 | $clientRepository = new ClientRepository(); 22 | 23 | if (isset($_REQUEST['delete'])) { 24 | $clientRepository->delete($_REQUEST['delete']); 25 | 26 | HTTP::redirectTrustedURL('registry.php'); 27 | } 28 | 29 | if (isset($_REQUEST['restore'])) { 30 | $clientRepository->restoreSecret($_REQUEST['restore']); 31 | 32 | HTTP::redirectTrustedURL('registry.php'); 33 | } 34 | 35 | $clients = $clientRepository->findAll(); 36 | 37 | $template = new \SimpleSAML_XHTML_Template($config, 'oauth2:registry_list'); 38 | $template->data['clients'] = $clients; 39 | $template->show(); 40 | -------------------------------------------------------------------------------- /www/resources/style.css: -------------------------------------------------------------------------------- 1 | table { 2 | width: 100%; 3 | } 4 | table tr th { 5 | text-align: right; 6 | vertical-align: top; 7 | padding-right: .6em; 8 | } 9 | table tr td { 10 | text-align: left; 11 | padding: 0px; 12 | width: 60%; 13 | } 14 | table tr td.header { 15 | padding-left: 5px; 16 | padding-top: 8px; 17 | font-weight: bold; 18 | font-size: 110%; 19 | } 20 | 21 | table tr td input,table tr td textarea { 22 | width: 90%; 23 | border: 1px solid #bbb; 24 | margin: 2px 5px; 25 | padding: 2px 4px; 26 | } 27 | 28 | table.metalist { 29 | border: 0; 30 | width: 100%; 31 | margin-bottom: 10px; 32 | } 33 | table.metalist tr { 34 | border: 1px solid black; 35 | } 36 | table.metalist tr td { 37 | padding: 10px 5px; 38 | } 39 | table.metalist tr th { 40 | background: #e5e5e5; 41 | font-variant: small-caps; 42 | } 43 | 44 | .button { 45 | background-color:#ffffff; 46 | border:1px solid #dcdcdc; 47 | display:inline-block; 48 | cursor:pointer; 49 | color:#666666!important; 50 | font-size:12px; 51 | padding:5px 12px; 52 | margin-right: 5px; 53 | text-decoration:none; 54 | width: auto; 55 | } 56 | .button:hover { 57 | background-color:#f6f6f6; 58 | } 59 | .button:active { 60 | position:relative; 61 | top:1px; 62 | } 63 | -------------------------------------------------------------------------------- /www/userinfo.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | use SimpleSAML\Modules\OAuth2\OAuth2ResourceServer; 12 | use SimpleSAML\Modules\OAuth2\Repositories\AccessTokenRepository; 13 | use SimpleSAML\Modules\OAuth2\Repositories\UserRepository; 14 | use Zend\Diactoros\Response\JsonResponse; 15 | use Zend\Diactoros\Response\SapiEmitter; 16 | use Zend\Diactoros\ServerRequestFactory; 17 | 18 | try { 19 | $server = OAuth2ResourceServer::getInstance(); 20 | $request = ServerRequestFactory::fromGlobals(); 21 | 22 | $authorization = $server->validateAuthenticatedRequest($request); 23 | 24 | $oauth2Attributes = $authorization->getAttributes(); 25 | $tokenId = $oauth2Attributes['oauth_access_token_id']; 26 | 27 | $accessTokenRepository = new AccessTokenRepository(); 28 | $userId = $accessTokenRepository->getUserId($tokenId); 29 | 30 | $userRepository = new UserRepository(); 31 | $attributes['attributes'] = $userRepository->getAttributes($userId); 32 | $attributes['username'] = $userId; 33 | 34 | $response = new JsonResponse($attributes); 35 | 36 | $emiter = new SapiEmitter(); 37 | $emiter->emit($response); 38 | } catch (Exception $e) { 39 | header('Content-type: text/plain; utf-8', true, 500); 40 | header('OAuth-Error: '.$e->getMessage()); 41 | 42 | print_r($e); 43 | } 44 | --------------------------------------------------------------------------------