(Grav GitSync) Automatic Commit from exu

This commit is contained in:
exu 2023-06-26 14:36:34 +02:00 committed by GitSync
parent 32e73f8ffa
commit e65df4b18d
476 changed files with 51116 additions and 0 deletions

View File

@ -0,0 +1,43 @@
# v2.2.0
## 03/30/2022
1. [](#new)
* Added Okta provider [#7](https://github.com/trilbymedia/grav-plugin-login-oauth2-extras/pull/7)
2. [](#bugfix)
* Fixed issues with TwitchHelix provider [#6](https://github.com/trilbymedia/grav-plugin-login-oauth2-extras/issues/6)
# v2.1.2
## 03/16/2022
1. [](#new)
* Added Keycloak provider [#5](https://github.com/trilbymedia/grav-plugin-login-oauth2-extras/pull/5)
2. [](#improved)
* Updated Twitch provider to TwitchHelix [#6](https://github.com/trilbymedia/grav-plugin-login-oauth2-extras/issues/6)
* Updated all vendor libraries to latest
# v2.1.1
## 12/02/2020
1. [](#improved)
* Azure - Add tenant option for OpenID scopes [#3](https://github.com/trilbymedia/grav-plugin-login-oauth2-extras/pull/3)
* Azure - Get profile picture form Azure [#3](https://github.com/trilbymedia/grav-plugin-login-oauth2-extras/pull/3)
* Azure - Get group memberships from Azure [#3](https://github.com/trilbymedia/grav-plugin-login-oauth2-extras/pull/3)
# v2.1.0
## 05/11/2020
1. [](#new)
* Added Patreon Provider
* Added Azure Provider [#1](https://github.com/trilbymedia/grav-plugin-login-oauth2-extras/pull/1)
# v2.0.1
## 04/27/2019
1. [](#new)
* Added Twitch Provider
# v2.0.0
## 04/26/2019
1. [](#new)
* ChangeLog started...

View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2019 Trilby Media
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,273 @@
# Login Login OAuth2 Extras Plugin
The **Login Login OAuth2 Extras** Plugin is for [Grav CMS](http://github.com/getgrav/grav). This plugin provides extra authenticatoin providers not included in the [Login OAuth2 Plugin](http://github.com/trilbymedia/grav-plugin-login-oauth2).
**NOTE:** Please use the same **Callback URIs** and configuration from **Login OAuth2 Plugin**.
Currently the plugin supports the following providers:
* **GitLab:** - https://docs.gitlab.com/ee/integration/oauth_provider.html
* **Discord:** - https://extrasapp.com/developers/docs/topics/oauth2
* **Slack:** - https://api.slack.com/docs/sign-in-with-slack
* **Jira:** - https://developer.atlassian.com/server/jira/platform/oauth/
* **Twitch** - https://dev.twitch.tv/docs/authentication/getting-tokens-oauth/
* **Azure** - https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app
* **Patreon** - https://docs.patreon.com/#oauth
* **Keycloak** - https://github.com/stevenmaguire/oauth2-keycloak
* **Okta** - https://github.com/foxworth42/oauth2-okta
If you wish to add a new provider, please open a pull request against this repo.
## Installation
Installing the Login Login OAuth2 Extras plugin can be done in one of two ways. The GPM (Grav Package Manager) installation method enables you to quickly and easily install the plugin with a simple terminal command, while the manual method enables you to do so via a zip file.
The simplest way to install this plugin is via the [Grav Package Manager (GPM)](http://learn.getgrav.org/advanced/grav-gpm) through your system's terminal (also called the command line). From the root of your Grav install type:
bin/gpm install login-oauth2-extras
This will install the Login Login OAuth2 Extras plugin into your `/user/plugins` directory within Grav. Its files can be found under `/your/site/grav/user/plugins/login-oauth2-extras`.
## Configuration
Before configuring this plugin, you should copy the `user/plugins/login-oauth2-extras/login-oauth2-extras.yaml` to `user/config/plugins/login-oauth2-extras.yaml` and only edit that copy.
Here is the default configuration and an explanation of available options:
```yaml
enabled: true
built_in_css: true
providers:
gitlab:
enabled: false
client_id: ''
client_secret: ''
domain:
options:
scope: ['read_user', 'openid']
discord:
enabled: false
client_id: ''
client_secret: ''
options:
scope: ['identify', 'email']
slack:
enabled: false
client_id: ''
client_secret: ''
options:
scope: ['users:read', 'users:read.email']
jira:
enabled: false
client_id: ''
client_secret: ''
options:
scope: ['read:jira-user']
twitch:
enabled: false
client_id: ''
client_secret: ''
options:
scope: ['user_read']
azure:
enabled: false
tenant: 'common'
client_id: ''
client_secret: ''
options:
scope: ['openid', 'email', 'profile', 'offline_access', 'User.Read']
get_groups: false
avatar_max_size: 240
patreon:
enabled: false
client_id: ''
client_secret: ''
options:
scope: ['users']
keycloak:
enabled: false
authserver_url: ''
realm: ''
client_id: ''
client_secret: ''
options:
scope: ['users']
userdata_login: ''
userdata_fullname: ''
userdata_email: ''
okta:
enabled: false
client_id:
client_secret:
issuer:
options:
scope: ['openid', 'email', 'profile']
admin:
enabled: true
built_in_css: true
providers:
gitlab:
enabled: false
client_id: ''
client_secret: ''
domain:
options:
scope: ['read_user', 'openid']
discord:
enabled: false
client_id: ''
client_secret: ''
options:
scope: ['identify', 'email']
slack:
enabled: false
client_id: ''
client_secret: ''
options:
scope: ['users:read', 'users:read.email']
jira:
enabled: false
client_id: ''
client_secret: ''
options:
scope: ['read:jira-user']
twitch:
enabled: false
client_id: ''
client_secret: ''
options:
scope: ['user_read']
azure:
enabled: false
tenant: 'common'
client_id: ''
client_secret: ''
options:
scope: ['openid', 'email', 'profile', 'offline_access', 'User.Read']
get_groups: false
avatar_max_size: 240
patreon:
enabled: false
client_id: ''
client_secret: ''
options:
scope: ['users']
keycloak:
enabled: false
authserver_url: ''
realm: ''
client_id: ''
client_secret: ''
options:
scope: ['users']
userdata_login: ''
userdata_fullname: ''
userdata_email: ''
okta:
enabled: false
client_id:
client_secret:
issuer:
options:
scope: ['openid', 'email', 'profile']
```
Note that if you use the admin plugin, a file with your configuration, and named login-oauth2-extras.yaml will be saved in the `user/config/plugins/` folder once the configuration is saved in the admin.
### OAuth2 Providers
#### GitLab
|Key |Description | Values |
|:---------------------|:---------------------------|:-------|
|enabled|Enable or disable this specific provider. This stops its showing as an valid login option| `true` \| [default: `false`] |
|client_id|The **Client ID** Provided by GitLab when you register an application for OAuth2 authentication | `<string>` |
|client_secret|The **Client Secret** Provided by GitLab when you register an application for OAuth2 authentication | `<string>` |
|domain|A custom GitLab domain| `<string>` |
|scope|An array of strings that define the OAuth2 scope. These can enable retrieving more data, but often require more permissions | e.g. `['read_user', 'openid']` |
#### Discord
|Key |Description | Values |
|:---------------------|:---------------------------|:-------|
|enabled|Enable or disable this specific provider. This stops its showing as an valid login option| `true` \| [default: `false`] |
|client_id|The **Client ID** Provided by Discord when you register an application for OAuth2 authentication | `<string>` |
|client_secret|The **Client Secret** Provided by Discord when you register an application for OAuth2 authentication | `<string>` |
|scope|An array of strings that define the OAuth2 scope. These can enable retrieving more data, but often require more permissions | e.g. `['identify', 'email']` |
#### Slack
|Key |Description | Values |
|:---------------------|:---------------------------|:-------|
|enabled|Enable or disable this specific provider. This stops its showing as an valid login option| `true` \| [default: `false`] |
|client_id|The **Client ID** Provided by Slack when you register an application for OAuth2 authentication | `<string>` |
|client_secret|The **Client Secret** Provided by Slack when you register an application for OAuth2 authentication | `<string>` |
|scope|An array of strings that define the OAuth2 scope. These can enable retrieving more data, but often require more permissions | e.g. `['users:read', 'users:read.email']` |
#### Jira
|Key |Description | Values |
|:---------------------|:---------------------------|:-------|
|enabled|Enable or disable this specific provider. This stops its showing as an valid login option| `true` \| [default: `false`] |
|client_id|The **Client ID** Provided by Jira when you register an application for OAuth2 authentication | `<string>` |
|client_secret|The **Client Secret** Provided by Jira when you register an application for OAuth2 authentication | `<string>` |
|scope|An array of strings that define the OAuth2 scope. These can enable retrieving more data, but often require more permissions | e.g. `['read:jira-user']` |
#### Twitch
|Key |Description | Values |
|:---------------------|:---------------------------|:-------|
|enabled|Enable or disable this specific provider. This stops its showing as an valid login option| `true` \| [default: `false`] |
|client_id|The **Client ID** Provided by Twitch when you register an application for OAuth2 authentication | `<string>` |
|client_secret|The **Client Secret** Provided by Twitch when you register an application for OAuth2 authentication | `<string>` |
|scope|An array of strings that define the OAuth2 scope. These can enable retrieving more data, but often require more permissions | e.g. `['user_read']` |
#### Azure
|Key |Description | Values |
|:---------------------|:---------------------------|:-------|
|enabled|Enable or disable this specific provider. This stops its showing as an valid login option| `true` \| [default: `false`] |
|tenant|The **Tenant ID** of your Azure AD tenant that you want to use. Use 'common' for all users, 'organizations' for Azure AD work or school accounts, 'consumers' for personal Microsoft accounts or a tenant id for accounts from a single Azure AD tenant.|`common`, `organizations`, `consumers`, e.g. `58673e44-617a-4d61-88b5-fb480759a841`|
|client_id|The **Client ID** Provided by Azure when you register an application for OAuth2 authentication | `<string>` |
|client_secret|The **Client Secret** Provided by Azure when you register an application for OAuth2 authentication | `<string>` |
|scope|An array of strings that define the OAuth2 scope. These can enable retrieving more data, but often require more permissions | e.g. `['openid', 'email', 'profile', 'offline_access', 'User.Read']` |
|get_groups|Add all the groups from Azure to the users, which includes transitive memberships. This needs at least the `GroupMember.Read.All` scope as well, which needs admin consent. **Warning**: if you save the users the groups will only be added, but not removed.| `true` \| [default: `false`] |
|avatar_max_size|The maximum size in pixels of the avatar to store. Azure does not provide all sizes, only 48x48, 64x64, 96x96, 120x120, 240x240, 360x360, 432x432, 504x504, and 648x648. | e.g. `240` |
#### Patreon
|Key |Description | Values |
|:---------------------|:---------------------------|:-------|
|enabled|Enable or disable this specific provider. This stops its showing as an valid login option| `true` \| [default: `false`] |
|client_id|The **Client ID** Provided by Patreon when you register an application for OAuth2 authentication | `<string>` |
|client_secret|The **Client Secret** Provided by Patreon when you register an application for OAuth2 authentication | `<string>` |
|scope|An array of strings that define the OAuth2 scope. These can enable retrieving more data, but often require more permissions | e.g. `['users']` |
#### Keycloak
|Key |Description | Values |
|:---------------------|:---------------------------|:-------|
|enabled|Enable or disable this specific provider. This stops its showing as an valid login option| `true` \| [default: `false`] |
|authserver_url| The **AuthServer URL** of your Keycloak server that you want to use. |`<string>` |
|realm| The **Realm** of your Keycloak server that you want to use. |`<string>` |
|client_id|The **Client ID** Provided by Keycloak when you register an application for OAuth2 authentication | `<string>` |
|client_secret|The **Client Secret** Provided by Keycloak when you register an application for OAuth2 authentication | `<string>` |
|encryption_algorithm| The **Encryption Algorithm** to be used, if your Keycloak instance is configured to use encryption. |`<string>` \| e.g. `RS256` |
|encryption_key| The contents of your public key or certificate that should be used for decryption, if your Keycloak instance is configured to use encryption. |`<string>` |
|scope|An array of strings that define the OAuth2 scope. These can enable retrieving more data, but often require more permissions | e.g. `['users']` |
|userdata_login| The **Login** key of the Keycloak user data.|`<string>` |
|userdata_fullname| The user's **full name** key of the Keycloak user data.|`<string>` \| e.g. `name` |
|userdata_email| The user's **email address** key of the Keycloak user data.|`<string>` \| e.g. `email` |
#### Okta
|Key |Description | Values |
|:---------------------|:---------------------------|:-------|
| enabled | Enable or disable this specific provider. This stops its showing as an valid login option | `true` \| [default: `false`] |
| client_id | The **Client ID** Provided by Okta when you register an application for OAuth2 authentication | `` |
| client_secret | The **Client Secret** Provided by Okta when you register an application for OAuth2 authentication | `` |
| issuer | The **Issuer** Provided by Okta when you register an application for OAuth2 authentication | `` |
| scope | An array of strings that define the OAuth2 scope. These can enable retrieving more data, but often require more permissions | default: `['openid', 'email', 'profile']` |

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,186 @@
<?php
namespace Grav\Plugin\Login\OAuth2\Providers;
use Grav\Common\Grav;
use GuzzleHttp\Promise;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
class AzureProvider extends ExtraProvider
{
protected $name = 'Azure';
protected $classname = 'TheNetworg\\OAuth2\\Client\\Provider\\Azure';
public function initProvider(array $options): void
{
$options += [
'clientId' => $this->config->get('providers.azure.client_id'),
'clientSecret' => $this->config->get('providers.azure.client_secret'),
'tenant' => $this->config->get('providers.azure.tenant'),
// Get access tokens for the Microsoft Graph API instead of the old Azure AD Graph.
'urlAPI' => 'https://graph.microsoft.com',
'resource' => 'https://graph.microsoft.com',
'redirectUri' => $this->getCallbackUri(),
];
parent::initProvider($options);
}
public function getAuthorizationUrl()
{
$options = ['state' => $this->state];
$options['scope'] = $this->config->get('providers.azure.options.scope');
return $this->provider->getAuthorizationUrl($options);
}
public function getUserData($user)
{
$name = $user->claim('name');
$data_user = [
'id' => $user->getId(),
'login' => $user->getUpn(),
'fullname' => $name,
'email' => $user->claim('email') ?: $user->getUpn(),
'azure' => [
// The avatar can be set by using avatar_url or avatar.
// Technically we're not setting a url because pictures from Azure are not public, so this contains a
// data url with a base64 encoded image.
'avatar' => $this->getAvatar($name),
'issuer' => $user->claim('iss'),
'tenant' => $user->getTenantId(),
]
];
$getGroups = $this->config->get('providers.azure.options.get_groups');
if ($getGroups)
{
$data_user['groups'] = $this->getUserGroups($name);
}
return $data_user;
}
public function getAvatar($name)
{
$avatarMaxSize = $this->config->get('providers.azure.options.avatar_max_size');
// This should already be validated to be at least 48, because that's the lowest available resolution, but just
// to be sure.
if ($avatarMaxSize < 48)
{
$avatarMaxSize = 48;
}
// First get the meta information for all the available pictures, there are versions from 48x48 to 648x648,
// depending on the size uploaded by the user.
// Use the beta endpoint to get the profile picture as the v1.0 endpoint only returns the picture if the user
// has a mailbox. See https://docs.microsoft.com/en-us/graph/known-issues#users
$photoMetaUrl = 'https://graph.microsoft.com/beta/me/photos/';
try
{
$photoMetaList = $this->provider->get($photoMetaUrl, $this->token);
}
catch (IdentityProviderException $e)
{
// User seems to have no picture.
Grav::instance()['log']->info('AzureProvider: failed to get photo for user \'' . $name .
'\'. Exception message: ' . $e->getMessage());
return null;
}
// Limit picture size by width or height, depending on which is larger.
$comparisonProperty = $photoMetaList[0]['height'] > $photoMetaList[0]['width'] ? 'height' : 'width';
// Filter pictures by maximum size that was configured.
$photoMetaList = array_filter(
$photoMetaList,
function ($photoMeta) use ($comparisonProperty, $avatarMaxSize)
{
return $photoMeta[$comparisonProperty] <= $avatarMaxSize;
}
);
// Get the metadata for the largest remaining picture.
$photoMeta = array_reduce(
$photoMetaList,
function($carry, $item) use ($comparisonProperty){
return $carry[$comparisonProperty] < $item[$comparisonProperty] ? $item : $carry;
},
[$comparisonProperty => -PHP_INT_MAX]
);
// Get the actual picture.
$photoUrl = $photoMetaUrl . $photoMeta['id'] . '/$value';
try
{
$photo = $this->provider->get($photoUrl, $this->token);
}
catch (IdentityProviderException $e)
{
// Getting the picture failed even though getting the meta succeeded.
Grav::instance()['log']->error('AzureProvider: failed to get photo for user \'' . $name .
'\'. Exception message: ' . $e->getMessage());
return null;
}
// Use a data url with a base64 encoded image since we need to provide a url for the avatar.
return 'data:' . $photoMeta['@odata.mediaContentType'] . ';base64,' . base64_encode($photo);
}
public function getUserGroups($name)
{
// Get the ids to the groups a user is member of, including transitive memberships.
$graphUrl = 'https://graph.microsoft.com/v1.0/';
$memberGroupsUri = $graphUrl . 'me/getMemberGroups';
$body = [ 'securityEnabledOnly' => true ];
try
{
$groupIds = $this->provider->post($memberGroupsUri, $body, $this->token);
}
catch (IdentityProviderException $e)
{
// User might not be able to join any groups (e.g. personal Microsoft account).
Grav::instance()['log']->info('AzureProvider: cannot get groups for user \'' . $name .
'\'. Exception message: ' . $e->getMessage());
return array();
}
// Get the whole group objects for each id in parallel by abusing Guzzle promises.
// Implementing this kind of parallelism in the lower level oauth2-azure and oauth2-client libraries would be
// better, but that might take a while, so doing it this way is faster for now.
// Start the requests to Microsoft Graph.
$promises = array();
foreach ($groupIds as $groupId)
{
$groupUrl = $graphUrl . 'groups/' . $groupId;
$promises[$groupId] = Promise\task(
function () use ($groupUrl)
{
return $this->provider->get($groupUrl, $this->token);
}
);
}
// Wait until all the requests complete.
$results = Promise\settle($promises)->wait();
// Get the actual groups or error messages from each request.
$groups = array();
foreach ($results as $groupId => $result)
{
if($result['state'] === Promise\PromiseInterface::FULFILLED)
{
$groups[$groupId] = $result['value'];
}
else
{
$message = $result['reason']->getMessage();
Grav::instance()['log']->error(
'AzureProvider: failed to get name for group \'' . $groupId . '\'. Exception message: ' . $message);
}
}
// Extract the names from the group objects
return array_column($groups, 'displayName');
}
}

View File

@ -0,0 +1,50 @@
<?php
namespace Grav\Plugin\Login\OAuth2\Providers;
class DiscordProvider extends ExtraProvider
{
protected $name = 'Discord';
protected $classname = 'Wohali\\OAuth2\\Client\\Provider\\Discord';
public function initProvider(array $options): void
{
$options += [
'clientId' => $this->config->get('providers.discord.client_id'),
'clientSecret' => $this->config->get('providers.discord.client_secret'),
'redirectUri' => $this->getCallbackUri(),
];
parent::initProvider($options);
}
public function getAuthorizationUrl()
{
$options = ['state' => $this->state];
$options['scope'] = $this->config->get('providers.discord.options.scope');
return $this->provider->getAuthorizationUrl($options);
}
public function getUserData($user)
{
$data = $user->toArray();
$avatar_url = "https://cdn.discordapp.com/avatars/{$data['id']}/{$data['avatar']}.jpg";
$data_user = [
'id' => $user->getId(),
'login' => $user->getUsername(),
'fullname' => $user->getUsername(),
'email' => $user->getEmail(),
'discord' => [
'discriminator' => $data['discriminator'],
'verified' => $data['verified'],
'locale' => $data['locale'],
'mfa_enabled' => $data['mfa_enabled'],
'avatar_url' => $avatar_url,
]
];
return $data_user;
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace Grav\Plugin\Login\OAuth2\Providers;
use Grav\Common\Data\Data;
use Grav\Common\Grav;
class ExtraProvider extends BaseProvider
{
public function __construct()
{
parent::__construct();
$admin = Grav::instance()['oauth2']->isAdmin();
$this->config = new Data(Grav::instance()['config']->get('plugins.login-oauth2-extras' . ($admin ? '.admin' : '')));
}
public function getAuthorizationUrl()
{
}
public function getUserData($user)
{
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace Grav\Plugin\Login\OAuth2\Providers;
class GitlabProvider extends ExtraProvider
{
protected $name = 'Gitlab';
protected $classname = 'Omines\\OAuth2\\Client\\Provider\\Gitlab';
public function initProvider(array $options): void
{
$domain = $this->config->get('providers.gitlab.domain', false);
$options += [
'clientId' => $this->config->get('providers.gitlab.client_id'),
'clientSecret' => $this->config->get('providers.gitlab.client_secret'),
];
if ($domain) {
$options += ['domain' => $domain];
}
parent::initProvider($options);
}
public function getAuthorizationUrl()
{
$options = ['state' => $this->state];
$options['scope'] = $this->config->get('providers.gitlab.options.scope');
return $this->provider->getAuthorizationUrl($options);
}
public function getUserData($user)
{
$data = $user->toArray();
$data_user = [
'id' => $user->getId(),
'login' => $user->getUsername(),
'fullname' => $user->getName(),
'email' => $user->getEmail(),
'gitlab' => [
'domain' => $user->getDomain(),
'location' => $data['location'],
'web_url' => $user->getProfileUrl(),
'avatar_url' => $user->getAvatarUrl(),
'active' => $user->isActive(),
'external' => $user->isExternal(),
'admin' => $user->isAdmin()
]
];
return $data_user;
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace Grav\Plugin\Login\OAuth2\Providers;
class JiraProvider extends BaseProvider
{
protected $name = 'Jira';
protected $classname = 'Mrjoops\\OAuth2\\Client\Provider\\Jira';
public function initProvider(array $options): void
{
$options += [
'clientId' => $this->config->get('providers.jira.client_id'),
'clientSecret' => $this->config->get('providers.jira.client_secret'),
'redirectUri' => $this->getCallbackUri(),
];
parent::initProvider($options);
}
public function getAuthorizationUrl()
{
$options = ['state' => $this->state];
$options['scope'] = $this->config->get('providers.jira.options.scope');
return $this->provider->getAuthorizationUrl($options);
}
public function getUserData($user)
{
$data_user = [
'id' => $user->getId(),
'login' => $user->getName(),
'fullname' => $user->getName(),
'email' => $user->getEmail(),
'jira' => [
'company' => $user->getName(),
'avatar_url' => $user->getAvatarUrl(),
],
];
return $data_user;
}
}

View File

@ -0,0 +1,53 @@
<?php
namespace Grav\Plugin\Login\OAuth2\Providers;
use Grav\Common\Grav;
use Stevenmaguire\OAuth2\Client\Provider\KeycloakResourceOwner;
use Stevenmaguire\OAuth2\Client\Provider\Keycloak;
class KeycloakProvider extends ExtraProvider
{
/** @var string */
protected $name = 'Keycloak';
/** @var string */
protected $classname = Keycloak::class;
public function initProvider(array $options): void
{
$options += [
'realm' => $this->config->get('providers.keycloak.realm'),
'clientId' => $this->config->get('providers.keycloak.client_id'),
'clientSecret' => $this->config->get('providers.keycloak.client_secret'),
'authServerUrl' => $this->config->get('providers.keycloak.authserver_url'),
'encryptionAlgorithm' => $this->config->get('providers.keycloak.encryption_algorithm'),
'encryptionKey' => $this->config->get('providers.keycloak.encryption_key'),
];
parent::initProvider($options);
}
/**
* @return string
*/
public function getAuthorizationUrl(): string
{
$options = ['state' => $this->state];
$options['scope'] = $this->config->get('providers.keycloak.options.scope');
return $this->provider->getAuthorizationUrl($options);
}
public function getUserData($user): array
{
$data = $user->toArray();
$data_user = [
'id' => $user->getId(),
'login' => $data[$this->config->get('providers.keycloak.userdata_login')],
'fullname' => $data[$this->config->get('providers.keycloak.userdata_fullname')],
'email' => $data[$this->config->get('providers.keycloak.userdata_email')],
'keycloak' => $data,
];
return $data_user;
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace Grav\Plugin\Login\OAuth2\Providers;
class OktaProvider extends ExtraProvider
{
protected $name = 'Okta';
protected $classname = 'Foxworth42\\OAuth2\\Client\\Provider\\Okta';
public function initProvider(array $options) : void
{
$options += [
'clientId' => $this->config->get('providers.okta.client_id'),
'clientSecret' => $this->config->get('providers.okta.client_secret'),
'issuer' => $this->config->get('providers.okta.issuer')
];
parent::initProvider($options);
}
public function getAuthorizationUrl()
{
$options = ['state' => $this->state];
$options['scope'] = $this->config->get('providers.okta.options.scope');
return $this->provider->getAuthorizationUrl($options);
}
public function getUserData($user)
{
$data_user = [
'id' => $user->getId(),
'login' => $user->getPreferredUsername(),
'fullname' => $user->getName(),
'email' => $user->getEmail(),
'okta' => [
'zone_info' => $user->getZoneInfo(),
'locale' => $user->getLocale()
]
];
return $data_user;
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace Grav\Plugin\Login\OAuth2\Providers;
class PatreonProvider extends ExtraProvider
{
protected $name = 'Patreon';
protected $classname = 'Gravure\\Patreon\\Oauth\\Provider\\Patreon';
public function initProvider(array $options): void
{
$options += [
'clientId' => $this->config->get('providers.patreon.client_id'),
'clientSecret' => $this->config->get('providers.patreon.client_secret'),
'redirectUri' => $this->getCallbackUri(),
];
parent::initProvider($options);
}
public function getAuthorizationUrl()
{
$options = ['state' => $this->state];
$options['scope'] = $this->config->get('providers.patreon.options.scope');
return $this->provider->getAuthorizationUrl($options);
}
public function getUserData($user)
{
$data = $user->toArray();
$data_user = [
'id' => $user->getId(),
'login' => $data['email'],
'fullname' => $data['full_name'],
'email' => $data['email'],
'patreon' => [
'avatar_url' => $user->getAvatar(),
'url' => $data['url'],
]
];
return $data_user;
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace Grav\Plugin\Login\OAuth2\Providers;
class SlackProvider extends BaseProvider
{
protected $name = 'Slack';
protected $classname = 'AdamPaterson\\OAuth2\\Client\\Provider\\Slack';
public function initProvider(array $options): void
{
$options += [
'clientId' => $this->config->get('providers.slack.client_id'),
'clientSecret' => $this->config->get('providers.slack.client_secret'),
'redirectUri' => $this->getCallbackUri(),
];
parent::initProvider($options);
}
public function getAuthorizationUrl()
{
$options = ['state' => $this->state];
$options['scope'] = $this->config->get('providers.slack.options.scope');
return $this->provider->getAuthorizationUrl($options);
}
public function getUserData($user)
{
$data = $user->toArray();
$data_user = [
'id' => $user->getId(),
'login' => $user->getName(),
'fullname' => $data['user']['profile']['real_name'],
'email' => $user->getEmail(),
'slack' => [
'location' => $data['user']['tz'],
'avatar_url' => $data['user']['profile']['image_512'],
]
];
return $data_user;
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace Grav\Plugin\Login\OAuth2\Providers;
class TwitchProvider extends ExtraProvider
{
protected $name = 'Twitch';
protected $classname = 'Vertisan\\OAuth2\\Client\\Provider\\TwitchHelix';
public function initProvider(array $options): void
{
$options += [
'clientId' => $this->config->get('providers.twitch.client_id'),
'clientSecret' => $this->config->get('providers.twitch.client_secret'),
'redirectUri' => $this->getCallbackUri(),
];
parent::initProvider($options);
}
public function getAuthorizationUrl()
{
$options = ['state' => $this->state];
$options['scope'] = $this->config->get('providers.twitch.options.scope');
return $this->provider->getAuthorizationUrl($options);
}
public function getUserData($user)
{
$data_user = [
'id' => $user->getId(),
'login' => $user->getLogin(),
'fullname' => $user->getDisplayName(),
'email' => $user->getEmail(),
'twitch' => [
'avatar_url' => $user->getProfileImageUrl(),
'bio' => $user->getDescription(),
'type' => $user->getType()
]
];
return $data_user;
}
}

View File

@ -0,0 +1,28 @@
{
"require": {
"wohali/oauth2-discord-new": "^1.0",
"adam-paterson/oauth2-slack": "^1.1",
"mrjoops/oauth2-jira": "^0.2.4",
"omines/oauth2-gitlab": "^3.1",
"thenetworg/oauth2-azure": "^1.4",
"gravure/oauth2-patreon": "dev-master",
"guzzlehttp/promises": "^1.3",
"vertisan/oauth2-twitch-helix": "^1.1",
"stevenmaguire/oauth2-keycloak": "^3.0",
"foxworth42/oauth2-okta": "^1.0"
},
"replace": {
"league/oauth2-client": "*",
"paragonie/random_compat": "*",
"psr/http-message": "*",
"guzzlehttp/psr7": "*",
"guzzlehttp/promises": "*",
"guzzlehttp/guzzle": "*"
},
"autoload": {
"psr-4": {
"Grav\\Plugin\\Login\\OAuth2\\": "classes/"
}
}
}

593
plugins/login-oauth2-extras/composer.lock generated Normal file
View File

@ -0,0 +1,593 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "675a0c1d9fd5a72913e53019ec35ea31",
"packages": [
{
"name": "adam-paterson/oauth2-slack",
"version": "1.1.3",
"source": {
"type": "git",
"url": "https://github.com/adam-paterson/oauth2-slack.git",
"reference": "ccc329eb3036a89d110227a4137e15d4a5661678"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/adam-paterson/oauth2-slack/zipball/ccc329eb3036a89d110227a4137e15d4a5661678",
"reference": "ccc329eb3036a89d110227a4137e15d4a5661678",
"shasum": ""
},
"require": {
"league/oauth2-client": "1.*|2.*",
"php": ">=5.6.0"
},
"require-dev": {
"mockery/mockery": "~0.9",
"phpunit/phpunit": "5.6",
"squizlabs/php_codesniffer": "~2.0"
},
"type": "library",
"autoload": {
"psr-4": {
"AdamPaterson\\OAuth2\\Client\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Adam Paterson",
"email": "hello@adampaterson.co.uk"
}
],
"description": "Slack OAuth 2.0 Client Provider for The PHP League OAuth2-Client",
"keywords": [
"Authentication",
"SSO",
"authorization",
"identity",
"idp",
"oauth",
"oauth2",
"single sign on",
"slack",
"slack api"
],
"support": {
"issues": "https://github.com/adam-paterson/oauth2-slack/issues",
"source": "https://github.com/adam-paterson/oauth2-slack/tree/master"
},
"time": "2017-06-20T14:43:31+00:00"
},
{
"name": "firebase/php-jwt",
"version": "v5.5.1",
"source": {
"type": "git",
"url": "https://github.com/firebase/php-jwt.git",
"reference": "83b609028194aa042ea33b5af2d41a7427de80e6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/firebase/php-jwt/zipball/83b609028194aa042ea33b5af2d41a7427de80e6",
"reference": "83b609028194aa042ea33b5af2d41a7427de80e6",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"require-dev": {
"phpunit/phpunit": ">=4.8 <=9"
},
"suggest": {
"paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present"
},
"type": "library",
"autoload": {
"psr-4": {
"Firebase\\JWT\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Neuman Vong",
"email": "neuman+pear@twilio.com",
"role": "Developer"
},
{
"name": "Anant Narayanan",
"email": "anant@php.net",
"role": "Developer"
}
],
"description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.",
"homepage": "https://github.com/firebase/php-jwt",
"keywords": [
"jwt",
"php"
],
"support": {
"issues": "https://github.com/firebase/php-jwt/issues",
"source": "https://github.com/firebase/php-jwt/tree/v5.5.1"
},
"time": "2021-11-08T20:18:51+00:00"
},
{
"name": "foxworth42/oauth2-okta",
"version": "v1.0.2",
"source": {
"type": "git",
"url": "https://github.com/foxworth42/oauth2-okta.git",
"reference": "0e7c2eb68f57eff8aafc4a3f0a1a1ec1c147c946"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/foxworth42/oauth2-okta/zipball/0e7c2eb68f57eff8aafc4a3f0a1a1ec1c147c946",
"reference": "0e7c2eb68f57eff8aafc4a3f0a1a1ec1c147c946",
"shasum": ""
},
"require": {
"league/oauth2-client": "^2.0",
"php": ">=7.1.0"
},
"require-dev": {
"php-coveralls/php-coveralls": "^2.1",
"phpunit/phpunit": "^7.0",
"squizlabs/php_codesniffer": "^3.4"
},
"type": "library",
"autoload": {
"psr-4": {
"Foxworth42\\OAuth2\\Client\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ed Walker",
"email": "github@foxwire.org"
}
],
"description": "Okta OAuth 2.0 Client Provider for The PHP League OAuth2-Client",
"keywords": [
"Authentication",
"authorization",
"client",
"oauth",
"oauth2",
"okta"
],
"support": {
"issues": "https://github.com/foxworth42/oauth2-okta/issues",
"source": "https://github.com/foxworth42/oauth2-okta/tree/v1.0.2"
},
"time": "2020-09-28T06:28:26+00:00"
},
{
"name": "gravure/oauth2-patreon",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/gravure/oauth2-patreon.git",
"reference": "32c5bb7c6cdfb0cbb4e396ca8e9cbde447f41f47"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/gravure/oauth2-patreon/zipball/32c5bb7c6cdfb0cbb4e396ca8e9cbde447f41f47",
"reference": "32c5bb7c6cdfb0cbb4e396ca8e9cbde447f41f47",
"shasum": ""
},
"require": {
"league/oauth2-client": "^1.0 || ^2.0",
"php": "5.6.* || >=7.0"
},
"default-branch": true,
"type": "library",
"autoload": {
"psr-4": {
"Gravure\\Patreon\\Oauth\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Daniël Klabbers",
"email": "daniel@klabbers.email",
"homepage": "http://luceos.com"
}
],
"description": "Provides Patreon OAuth 2.0 support for PHP League's OAuth 2.0 Client.",
"keywords": [
"league",
"oauth2",
"patreon"
],
"support": {
"issues": "https://github.com/gravure/oauth2-patreon/issues",
"source": "https://github.com/gravure/oauth2-patreon"
},
"time": "2018-02-06T12:41:19+00:00"
},
{
"name": "mrjoops/oauth2-jira",
"version": "v0.2.4",
"source": {
"type": "git",
"url": "https://github.com/mrjoops/oauth2-jira.git",
"reference": "9c270f7a70ea13c8d844676cddf17977991cb347"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/mrjoops/oauth2-jira/zipball/9c270f7a70ea13c8d844676cddf17977991cb347",
"reference": "9c270f7a70ea13c8d844676cddf17977991cb347",
"shasum": ""
},
"require": {
"league/oauth2-client": "^2.0"
},
"require-dev": {
"mockery/mockery": "^1.2",
"phpunit/phpunit": "^5.0",
"squizlabs/php_codesniffer": "^3.3"
},
"type": "library",
"autoload": {
"psr-4": {
"Mrjoops\\OAuth2\\Client\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Alexandre Lahure",
"email": "alexandre@lahu.re"
}
],
"description": "Jira OAuth 2.0 support for the PHP League's OAuth 2.0 Client",
"keywords": [
"authorisation",
"authorization",
"client",
"jira",
"oauth",
"oauth2"
],
"support": {
"issues": "https://github.com/mrjoops/oauth2-jira/issues",
"source": "https://github.com/mrjoops/oauth2-jira/tree/develop"
},
"time": "2018-11-11T19:49:42+00:00"
},
{
"name": "omines/oauth2-gitlab",
"version": "3.4.0",
"source": {
"type": "git",
"url": "https://github.com/omines/oauth2-gitlab.git",
"reference": "0c37361c54fae71a85350c445bda1834db5859af"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/omines/oauth2-gitlab/zipball/0c37361c54fae71a85350c445bda1834db5859af",
"reference": "0c37361c54fae71a85350c445bda1834db5859af",
"shasum": ""
},
"require": {
"league/oauth2-client": "^2.4.1",
"php": ">=7.2"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.0",
"guzzlehttp/psr7": "^1.6",
"http-interop/http-factory-guzzle": "^1.0",
"m4tthumphrey/php-gitlab-api": "^10.0|^11.0",
"mockery/mockery": "^1.0",
"php-http/guzzle7-adapter": "^0.1",
"phpunit/phpunit": "^8.0|^9.0"
},
"suggest": {
"m4tthumphrey/php-gitlab-api": "For further API usage using the acquired OAuth2 token"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.x-dev"
}
},
"autoload": {
"psr-4": {
"Omines\\OAuth2\\Client\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Niels Keurentjes",
"email": "niels.keurentjes@omines.com",
"homepage": "https://www.omines.nl/"
}
],
"description": "GitLab OAuth 2.0 Client Provider for The PHP League OAuth2-Client",
"keywords": [
"authorisation",
"authorization",
"client",
"gitlab",
"oauth",
"oauth2"
],
"support": {
"issues": "https://github.com/omines/oauth2-gitlab/issues",
"source": "https://github.com/omines/oauth2-gitlab/tree/3.4.0"
},
"time": "2021-02-08T12:15:55+00:00"
},
{
"name": "stevenmaguire/oauth2-keycloak",
"version": "3.0.0",
"source": {
"type": "git",
"url": "https://github.com/stevenmaguire/oauth2-keycloak.git",
"reference": "645b84107b82a08cfed9c101081eb8548ea5be11"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/stevenmaguire/oauth2-keycloak/zipball/645b84107b82a08cfed9c101081eb8548ea5be11",
"reference": "645b84107b82a08cfed9c101081eb8548ea5be11",
"shasum": ""
},
"require": {
"firebase/php-jwt": "~4.0|~5.0",
"league/oauth2-client": "^2.0 <2.3.0"
},
"require-dev": {
"mockery/mockery": "~0.9",
"phpunit/phpunit": "~4.0",
"squizlabs/php_codesniffer": "~2.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Stevenmaguire\\OAuth2\\Client\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Steven Maguire",
"email": "stevenmaguire@gmail.com",
"homepage": "https://github.com/stevenmaguire"
}
],
"description": "Keycloak OAuth 2.0 Client Provider for The PHP League OAuth2-Client",
"keywords": [
"authorisation",
"authorization",
"client",
"keycloak",
"oauth",
"oauth2"
],
"support": {
"issues": "https://github.com/stevenmaguire/oauth2-keycloak/issues",
"source": "https://github.com/stevenmaguire/oauth2-keycloak/tree/3.0.0"
},
"time": "2022-01-23T18:01:00+00:00"
},
{
"name": "thenetworg/oauth2-azure",
"version": "v1.4.0",
"source": {
"type": "git",
"url": "https://github.com/TheNetworg/oauth2-azure.git",
"reference": "c57dcb63a925c29e744bffa4a079a95680dd5faf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/TheNetworg/oauth2-azure/zipball/c57dcb63a925c29e744bffa4a079a95680dd5faf",
"reference": "c57dcb63a925c29e744bffa4a079a95680dd5faf",
"shasum": ""
},
"require": {
"firebase/php-jwt": "~3.0||~4.0||~5.0",
"league/oauth2-client": "~2.0",
"php": ">=5.5.0"
},
"type": "library",
"autoload": {
"psr-4": {
"TheNetworg\\OAuth2\\Client\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jan Hajek",
"email": "jan.hajek@thenetw.org",
"homepage": "https://thenetw.org"
}
],
"description": "Azure Active Directory OAuth 2.0 Client Provider for The PHP League OAuth2-Client",
"keywords": [
"SSO",
"aad",
"authorization",
"azure",
"azure active directory",
"client",
"microsoft",
"oauth",
"oauth2",
"windows azure"
],
"support": {
"issues": "https://github.com/TheNetworg/oauth2-azure/issues",
"source": "https://github.com/TheNetworg/oauth2-azure/tree/master"
},
"time": "2018-10-02T08:54:26+00:00"
},
{
"name": "vertisan/oauth2-twitch-helix",
"version": "1.1.2",
"source": {
"type": "git",
"url": "https://github.com/vertisan/oauth2-twitch-helix.git",
"reference": "67cac44b4adf113aee026424b435975a026db0e9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/vertisan/oauth2-twitch-helix/zipball/67cac44b4adf113aee026424b435975a026db0e9",
"reference": "67cac44b4adf113aee026424b435975a026db0e9",
"shasum": ""
},
"require": {
"league/oauth2-client": "^2.2.1",
"php": ">=5.6.0"
},
"require-dev": {
"ext-json": "*",
"jakub-onderka/php-parallel-lint": "^1.0",
"mockery/mockery": "^1.2",
"phpunit/phpunit": "^5.7",
"squizlabs/php_codesniffer": "^3.4"
},
"type": "library",
"autoload": {
"psr-4": {
"Vertisan\\OAuth2\\Client\\Provider\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Paweł Farys",
"email": "pmg.farys@gmail.com",
"homepage": "https://github.com/vertisan"
}
],
"description": "Twitch (new version Helix) OAuth 2.0 Client Provider for The PHP League OAuth2-Client",
"keywords": [
"client",
"helix",
"league",
"oauth",
"package",
"twitch"
],
"support": {
"issues": "https://github.com/vertisan/oauth2-twitch-helix/issues",
"source": "https://github.com/vertisan/oauth2-twitch-helix/tree/1.1.2"
},
"time": "2022-02-03T20:38:26+00:00"
},
{
"name": "wohali/oauth2-discord-new",
"version": "1.1.0",
"source": {
"type": "git",
"url": "https://github.com/wohali/oauth2-discord-new.git",
"reference": "0dcb5059cded358f55ae566de9621652cf8542c6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/wohali/oauth2-discord-new/zipball/0dcb5059cded358f55ae566de9621652cf8542c6",
"reference": "0dcb5059cded358f55ae566de9621652cf8542c6",
"shasum": ""
},
"require": {
"league/oauth2-client": "^2.0"
},
"conflict": {
"team-reflex/oauth2-discord": ">=1.0"
},
"require-dev": {
"mockery/mockery": "~1.3.0",
"php-parallel-lint/php-parallel-lint": "~0.9",
"phpunit/phpunit": "~8.0",
"squizlabs/php_codesniffer": "^2.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Wohali\\OAuth2\\Client\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Joan Touzet",
"email": "code@atypical.net",
"homepage": "https://github.com/wohali"
}
],
"description": "Discord OAuth 2.0 Client Provider for The PHP League OAuth2-Client",
"keywords": [
"authorisation",
"authorization",
"client",
"discord",
"oauth",
"oauth2"
],
"support": {
"issues": "https://github.com/wohali/oauth2-discord-new/issues",
"source": "https://github.com/wohali/oauth2-discord-new/tree/master"
},
"time": "2020-06-12T07:27:09+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {
"gravure/oauth2-patreon": 20
},
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": [],
"plugin-api-version": "2.2.0"
}

View File

@ -0,0 +1,64 @@
.form-oauth2 button.discord {
background: #7289DA;
}
.form-oauth2 button.discord:hover {
background: #687DC7;
}
.form-oauth2 button.slack {
background: #56B68B;
}
.form-oauth2 button.slack:hover {
background: #3D9D72;
}
.form-oauth2 button.jira {
background: #0747A6;
}
.form-oauth2 button.jira:hover {
background: #095cd7;
}
.form-oauth2 button.gitlab {
background: #fc6c26;
}
.form-oauth2 button.gitlab:hover {
background: #E3530D;
}
.form-oauth2 button.twitch {
background: #6440A4;
}
.form-oauth2 button.twitch:hover {
background: #55368B;
}
.form-oauth2 button.azure {
background: #2F2F2F;
font-family: Segoe UI, Frutiger, Frutiger Linotype, Dejavu Sans, Helvetica Neue, Arial, sans-serif;
font-weight: 600;
}
.form-oauth2 button.patreon {
background: #002C49;
}
.form-oauth2 button.patreon:hover {
background: #004674;
}
.form-oauth2 button.keycloak {
background: #C9C9C9;
color: #333333;
}
.form-oauth2 button.keycloak:hover {
background: #33C6E9;
}
.form-oauth2 button.okta {
background: #e6e6e6;
color: #333333;
}
.form-oauth2 button.okta:hover {
background: #9ba9bd;
}

View File

@ -0,0 +1,10 @@
PLUGIN_LOGIN_OAUTH2_EXTRAS:
AUTHSERVER_URL: "Keycloak server URL"
REALM: "Keycloak realm name"
ENCRYPTION_ALGORITHM: "Encryption algorithm"
ENCRYPTION_KEY: "Encryption key or certificate (content)"
USERDATA_LOGIN: "Login key"
USERDATA_FULLNAME: "FullName key"
USERDATA_EMAIL: "Email key"
OKTA:
ISSUER: "Issuer"

View File

@ -0,0 +1,109 @@
<?php
namespace Grav\Plugin;
use Grav\Common\Plugin;
use RocketTheme\Toolbox\Event\Event;
/**
* Class LoginOAuth2ExtrasPlugin
* @package Grav\Plugin
*/
class LoginOAuth2ExtrasPlugin extends Plugin
{
protected $admin = false;
/**
* @return array
*
* The getSubscribedEvents() gives the core a list of events
* that the plugin wants to listen to. The key of each
* array section is the event that the plugin listens to
* and the value (in the form of an array) contains the
* callable (or function) as well as the priority. The
* higher the number the higher the priority.
*/
public static function getSubscribedEvents()
{
return [
'onPluginsInitialized' => [
['autoload', 100000],
['onPluginsInitialized', 0]
],
'onTwigLoader' => ['onTwigLoader', 0],
'onTwigSiteVariables' => ['onTwigSiteVariables', 0],
'onTwigTemplatePaths' => ['onTwigTemplatePaths', 0],
];
}
/**
* Initialize the plugin
*/
public function onPluginsInitialized()
{
if ($this->isAdmin() && $this->grav['config']->get('plugins.login-oauth2.admin.enabled')) {
$this->admin = true;
}
// Don't proceed if we are in the admin plugin
if ( $this->isAdmin() && !$this->admin) {
return;
}
$this->addEnabledProviders();
}
/**
* [onPluginsInitialized:100000] Composer autoload.
*
* @return ClassLoader
*/
public function autoload()
{
return require __DIR__ . '/vendor/autoload.php';
}
public function onTwigLoader()
{
$media_paths = $this->grav['locator']->findResources('plugins://login-oauth2-extras/media');
foreach(array_reverse($media_paths) as $images_path) {
$this->grav['twig']->addPath($images_path, 'oauth2-media');
}
}
/**
* [onTwigTemplatePaths] Add twig paths to plugin templates.
*/
public function onTwigTemplatePaths()
{
$twig = $this->grav['twig'];
$twig->twig_paths[] = __DIR__ . '/templates';
}
public function onTwigSiteVariables()
{
// add CSS for frontend if required
if ((!$this->isAdmin() && $this->config->get('plugins.login-oauth2-extras.built_in_css')) ||
($this->admin && $this->config->get('plugins.login-oauth2-extras.admin.built_in_css'))) {
$this->grav['assets']->add('plugin://login-oauth2-extras/css/login-oauth2-extras.css');
}
}
protected function addEnabledProviders()
{
if (isset($this->grav['oauth2'])) {
$oauth2 = $this->grav['oauth2'];
if ($this->admin) {
$providers = $this->config->get('plugins.login-oauth2-extras.admin.providers', []);
} else {
$providers = $this->config->get('plugins.login-oauth2-extras.providers', []);
}
foreach ($providers as $provider => $options) {
if ($options['enabled']) {
$oauth2->addProvider($provider, $options);
}
}
}
}
}

View File

@ -0,0 +1,136 @@
enabled: true
built_in_css: true
providers:
gitlab:
enabled: false
client_id: ''
client_secret: ''
domain:
options:
scope: ['read_user', 'openid']
discord:
enabled: false
client_id: ''
client_secret: ''
options:
scope: ['identify', 'email']
slack:
enabled: false
client_id: ''
client_secret: ''
options:
scope: ['users:read', 'users:read.email']
jira:
enabled: false
client_id: ''
client_secret: ''
options:
scope: ['read:jira-user']
twitch:
enabled: false
client_id: ''
client_secret: ''
options:
scope: ['user_read']
azure:
enabled: false
tenant: 'common'
client_id: ''
client_secret: ''
options:
scope: ['openid', 'email', 'profile', 'offline_access', 'User.Read']
get_groups: false
avatar_max_size: 240
patreon:
enabled: false
client_id: ''
client_secret: ''
options:
scope: ['users']
keycloak:
enabled: false
authserver_url: ''
realm: ''
client_id: ''
client_secret: ''
options:
scope: ['users']
userdata_login: ''
userdata_fullname: ''
userdata_email: ''
okta:
enabled: false
client_id:
client_secret:
issuer:
options:
scope: ['openid', 'email', 'profile']
admin:
enabled: true
built_in_css: true
providers:
gitlab:
enabled: false
client_id: ''
client_secret: ''
domain:
options:
scope: ['read_user', 'openid']
discord:
enabled: false
client_id: ''
client_secret: ''
options:
scope: ['identify', 'email']
slack:
enabled: false
client_id: ''
client_secret: ''
options:
scope: ['users:read', 'users:read.email']
jira:
enabled: false
client_id: ''
client_secret: ''
options:
scope: ['read:jira-user']
twitch:
enabled: false
client_id: ''
client_secret: ''
options:
scope: ['user_read']
azure:
enabled: false
tenant: 'common'
client_id: ''
client_secret: ''
options:
scope: ['openid', 'email', 'profile', 'offline_access', 'User.Read']
get_groups: false
avatar_max_size: 240
patreon:
enabled: false
client_id: ''
client_secret: ''
options:
scope: ['users']
keycloak:
enabled: false
authserver_url: ''
realm: ''
client_id: ''
client_secret: ''
options:
scope: ['users']
userdata_login: ''
userdata_fullname: ''
userdata_email: ''
okta:
enabled: false
client_id:
client_secret:
issuer:
options:
scope: ['openid', 'email', 'profile']

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="21" height="21" viewBox="0 0 21 21"><title>MS-SymbolLockup</title><rect x="1" y="1" width="9" height="9" fill="#f25022"/><rect x="1" y="11" width="9" height="9" fill="#00a4ef"/><rect x="11" y="1" width="9" height="9" fill="#7fba00"/><rect x="11" y="11" width="9" height="9" fill="#ffb900"/></svg>

After

Width:  |  Height:  |  Size: 343 B

View File

@ -0,0 +1 @@
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 245 240"><style>.st0{fill:#FFFFFF;}</style><path class="st0" d="M104.4 103.9c-5.7 0-10.2 5-10.2 11.1s4.6 11.1 10.2 11.1c5.7 0 10.2-5 10.2-11.1.1-6.1-4.5-11.1-10.2-11.1zM140.9 103.9c-5.7 0-10.2 5-10.2 11.1s4.6 11.1 10.2 11.1c5.7 0 10.2-5 10.2-11.1s-4.5-11.1-10.2-11.1z"/><path class="st0" d="M189.5 20h-134C44.2 20 35 29.2 35 40.6v135.2c0 11.4 9.2 20.6 20.5 20.6h113.4l-5.3-18.5 12.8 11.9 12.1 11.2 21.5 19V40.6c0-11.4-9.2-20.6-20.5-20.6zm-38.6 130.6s-3.6-4.3-6.6-8.1c13.1-3.7 18.1-11.9 18.1-11.9-4.1 2.7-8 4.6-11.5 5.9-5 2.1-9.8 3.5-14.5 4.3-9.6 1.8-18.4 1.3-25.9-.1-5.7-1.1-10.6-2.7-14.7-4.3-2.3-.9-4.8-2-7.3-3.4-.3-.2-.6-.3-.9-.5-.2-.1-.3-.2-.4-.3-1.8-1-2.8-1.7-2.8-1.7s4.8 8 17.5 11.8c-3 3.8-6.7 8.3-6.7 8.3-22.1-.7-30.5-15.2-30.5-15.2 0-32.2 14.4-58.3 14.4-58.3 14.4-10.8 28.1-10.5 28.1-10.5l1 1.2c-18 5.2-26.3 13.1-26.3 13.1s2.2-1.2 5.9-2.9c10.7-4.7 19.2-6 22.7-6.3.6-.1 1.1-.2 1.7-.2 6.1-.8 13-1 20.2-.2 9.5 1.1 19.7 3.9 30.1 9.6 0 0-7.9-7.5-24.9-12.7l1.4-1.6s13.7-.3 28.1 10.5c0 0 14.4 26.1 14.4 58.3 0 0-8.5 14.5-30.6 15.2z"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1 @@
<svg aria-labelledby="simpleicons-gitlab-icon" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title id="simpleicons-gitlab-icon">GitLab icon</title><path d="M23.955 13.587l-1.342-4.135-2.664-8.189c-.135-.423-.73-.423-.867 0L16.418 9.45H7.582L4.919 1.263C4.783.84 4.185.84 4.05 1.26L1.386 9.449.044 13.587c-.121.375.014.789.331 1.023L12 23.054l11.625-8.443c.318-.235.453-.647.33-1.024"/></svg>

After

Width:  |  Height:  |  Size: 412 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

@ -0,0 +1,29 @@
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 400 134.7" style="enable-background:new 0 0 400 134.7;" xml:space="preserve">
<style type="text/css">
.st0{fill:#007DC1;}
</style>
<g>
<g>
<g>
<path class="st0" d="M50.3,33.8C22.5,33.8,0,56.3,0,84.1s22.5,50.3,50.3,50.3s50.3-22.5,50.3-50.3S78.1,33.8,50.3,33.8z
M50.3,109.3c-13.9,0-25.2-11.3-25.2-25.2s11.3-25.2,25.2-25.2s25.2,11.3,25.2,25.2S64.2,109.3,50.3,109.3z"/>
</g>
<path class="st0" d="M138.7,101c0-4,4.8-5.9,7.6-3.1c12.6,12.8,33.4,34.8,33.5,34.9c0.3,0.3,0.6,0.8,1.8,1.2
c0.5,0.2,1.3,0.2,2.2,0.2l22.7,0c4.1,0,5.3-4.7,3.4-7.1l-37.6-38.5l-2-2c-4.3-5.1-3.8-7.1,1.1-12.3L201.2,41c1.9-2.4,0.7-7-3.5-7
h-20.6c-0.8,0-1.4,0-2,0.2c-1.2,0.4-1.7,0.8-2,1.2c-0.1,0.1-16.6,17.9-26.8,28.8c-2.8,3-7.8,1-7.8-3.1l0-57.1c0-2.9-2.4-4-4.3-4
h-16.8c-2.9,0-4.3,1.9-4.3,3.6v126.6c0,2.9,2.4,3.7,4.4,3.7h16.8c2.6,0,4.3-1.9,4.3-3.8v-1.3V101z"/>
<path class="st0" d="M275.9,129.6l-1.8-16.8c-0.2-2.3-2.4-3.9-4.7-3.5c-1.3,0.2-2.6,0.3-3.9,0.3c-13.4,0-24.3-10.5-25.1-23.8
c0-0.4,0-0.9,0-1.4V63.8c0-2.7,2-4.9,4.7-4.9l22.5,0c1.6,0,4-1.4,4-4.3V38.7c0-3.1-2-4.7-3.8-4.7h-22.7c-2.6,0-4.7-1.9-4.8-4.5
l0-25.5c0-1.6-1.2-4-4.3-4h-16.7c-2.1,0-4.1,1.3-4.1,3.9c0,0,0,81.5,0,81.9c0.7,27.2,23,48.9,50.3,48.9c2.3,0,4.5-0.2,6.7-0.5
C274.6,133.9,276.2,131.9,275.9,129.6z"/>
</g>
<g>
<path class="st0" d="M397.1,108.5c-14.2,0-16.4-5.1-16.4-24.2c0-0.1,0-0.1,0-0.2l0-45.9c0-1.6-1.2-4.3-4.4-4.3h-16.8
c-2.1,0-4.4,1.7-4.4,4.3l0,2.1c-7.3-4.2-15.8-6.6-24.8-6.6c-27.8,0-50.3,22.5-50.3,50.3c0,27.8,22.5,50.3,50.3,50.3
c12.5,0,23.9-4.6,32.7-12.1c4.7,7.2,12.3,12,24.2,12.1c2,0,12.8,0.4,12.8-4.7v-17.9C400,110.2,398.8,108.5,397.1,108.5z
M330.4,109.3c-13.9,0-25.2-11.3-25.2-25.2c0-13.9,11.3-25.2,25.2-25.2c13.9,0,25.2,11.3,25.2,25.2
C355.5,98,344.2,109.3,330.4,109.3z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 100 96" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g>
<path d="M64.11,0.1C44.259,0.1 28.109,16.249 28.109,36.099C28.109,55.888 44.259,71.989 64.11,71.989C83.9,71.989 100,55.888 100,36.099C100,16.249 83.9,0.1 64.11,0.1" style="fill:white;"/>
<path d="M0.012,95.988L17.59,95.988L17.59,0.1L0.012,0.1L0.012,95.988Z" style="fill:white;"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 764 B

View File

@ -0,0 +1 @@
<svg aria-labelledby="simpleicons-slack-icon" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title id="simpleicons-slack-icon">Slack icon</title><path d="M9.879 10.995l1.035 3.085 3.205-1.074-1.035-3.074-3.205 1.08v-.017z"/><path d="M18.824 14.055l-1.555.521.54 1.61c.218.65-.135 1.355-.786 1.574-.15.045-.285.067-.435.063-.511-.015-.976-.338-1.155-.849l-.54-1.607-3.21 1.073.539 1.608c.211.652-.135 1.358-.794 1.575-.15.048-.285.067-.435.064-.51-.015-.976-.34-1.156-.85l-.539-1.619-1.561.524c-.15.045-.285.061-.435.061-.511-.016-.976-.345-1.155-.855-.225-.66.135-1.364.78-1.575l1.56-.525L7.5 11.76l-1.551.525c-.141.048-.285.066-.428.064-.495-.016-.975-.338-1.141-.848-.209-.65.135-1.354.796-1.574l1.56-.52-.54-1.605c-.21-.654.136-1.359.796-1.575.659-.22 1.363.136 1.574.783l.539 1.608L12.3 7.544l-.54-1.605c-.209-.645.135-1.35.789-1.574.652-.211 1.359.135 1.575.791l.54 1.621 1.555-.51c.651-.219 1.356.135 1.575.779.218.654-.135 1.359-.784 1.575l-1.557.524 1.035 3.086 1.551-.516c.652-.211 1.358.135 1.575.795.22.66-.135 1.365-.779 1.574l-.011-.029zm4.171-5.356C20.52.456 16.946-1.471 8.699 1.005.456 3.479-1.471 7.051 1.005 15.301c2.475 8.245 6.046 10.17 14.296 7.694 8.245-2.475 10.17-6.046 7.694-14.296z"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M2.149 0l-1.612 4.119v16.836h5.731v3.045h3.224l3.045-3.045h4.657l6.269-6.269v-14.686h-21.314zm19.164 13.612l-3.582 3.582h-5.731l-3.045 3.045v-3.045h-4.836v-15.045h17.194v11.463zm-3.582-7.343v6.262h-2.149v-6.262h2.149zm-5.731 0v6.262h-2.149v-6.262h2.149z" fill-rule="evenodd" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 394 B

View File

@ -0,0 +1,4 @@
<button name="oauth2" type="submit" value="azure" class="azure">
{% include('@oauth2-media/azure.svg') %}
<span>Sign in with Microsoft</span>
</button>

View File

@ -0,0 +1,4 @@
<button name="oauth2" type="submit" value="discord" class="discord">
{% include('@oauth2-media/discord.svg') %}
<span>Discord</span>
</button>

View File

@ -0,0 +1,4 @@
<button name="oauth2" type="submit" value="gitlab" class="gitlab">
{% include('@oauth2-media/gitlab.svg') %}
<span>GitLab</span>
</button>

View File

@ -0,0 +1,4 @@
<button name="oauth2" type="submit" value="jira" class="jira">
{% include('@oauth2-media/jira.svg') %}
<span>Jira</span>
</button>

View File

@ -0,0 +1,4 @@
<button name="oauth2" type="submit" value="keycloak" class="keycloak">
<span style="font-size:30px">🔐</span>
<span>Login with Keycloak</span>
</button>

View File

@ -0,0 +1,4 @@
<button name="oauth2" type="submit" value="okta" class="okta">
{% include('@oauth2-media/okta.svg') %}
<span>Okta</span>
</button>

View File

@ -0,0 +1,4 @@
<button name="oauth2" type="submit" value="patreon" class="patreon">
{% include('@oauth2-media/patreon.svg') %}
<span>Patreon</span>
</button>

View File

@ -0,0 +1,4 @@
<button name="oauth2" type="submit" value="slack" class="slack">
{% include('@oauth2-media/slack.svg') %}
<span>Slack</span>
</button>

View File

@ -0,0 +1,4 @@
<button name="oauth2" type="submit" value="twitch" class="twitch">
{% include('@oauth2-media/twitch.svg') %}
<span>Twitch</span>
</button>

View File

@ -0,0 +1,3 @@
composer.lock
/vendor/
/build/

View File

@ -0,0 +1,35 @@
filter:
excluded_paths: [test/*]
checks:
php:
code_rating: true
remove_extra_empty_lines: true
remove_php_closing_tag: true
remove_trailing_whitespace: true
fix_use_statements:
remove_unused: true
preserve_multiple: false
preserve_blanklines: true
order_alphabetically: true
fix_php_opening_tag: true
fix_linefeed: true
fix_line_ending: true
fix_identation_4spaces: true
fix_doc_comments: true
tools:
external_code_coverage:
timeout: 600
runs: 3
php_analyzer: true
php_code_coverage: false
php_code_sniffer:
config:
standard: PSR2
filter:
paths: ['src']
php_loc:
enabled: true
excluded_dirs: [vendor, test]
php_cpd:
enabled: true
excluded_dirs: [vendor, test]

View File

@ -0,0 +1,22 @@
language: php
dist: trusty
php:
- 5.6
- 7.0
- 7.1
- hhvm
before_script:
- travis_retry composer self-update
- travis_retry composer install --no-interaction --prefer-source --dev
- travis_retry phpenv rehash
script:
- ./vendor/bin/phpcs --standard=psr2 src/
- ./vendor/bin/phpunit --coverage-text --coverage-clover=coverage.clover
after_script:
- wget https://scrutinizer-ci.com/ocular.phar
- php ocular.phar code-coverage:upload --format=php-clover coverage.clover

View File

@ -0,0 +1,42 @@
# Contributing
Contributions are **welcome** and will be fully **credited**.
I accept contributions via Pull Requests on [Github](https://github.com/adam-paterson/oauth2-slack).
## Pull Requests
- **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](http://pear.php.net/package/PHP_CodeSniffer).
- **Add tests!** - Your patch won't be accepted if it doesn't have tests.
- **Document any change in behaviour** - Make sure the README and any other relevant documentation are kept up-to-date.
- **Consider our release cycle** - We try to follow SemVer. Randomly breaking public APIs is not an option.
- **Create topic branches** - Don't ask us to pull from your master branch.
- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.
- **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please squash them before submitting.
- **Ensure tests pass!** - Please run the tests (see below) before submitting your pull request, and make sure they pass. We won't accept a patch until all tests pass.
- **Ensure no coding standards violations** - Please run PHP Code Sniffer using the PSR-2 standard (see below) before submitting your pull request. A violation will cause the build to fail, so please make sure there are no violations. We can't accept a patch if the build fails.
## Running Tests
``` bash
$ ./vendor/bin/phpunit
```
## Running PHP Code Sniffer
``` bash
$ ./vendor/bin/phpcs src --standard=psr2 -sp
```
**Happy coding**!

View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015 Adam Paterson
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,95 @@
# Slack Provider for OAuth 2.0 Client
[![Latest Version](https://img.shields.io/github/release/adam-paterson/oauth2-slack.svg?style=flat-square)](https://github.com/adam-paterson/oauth2-slack/releases)
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md)
[![Build Status](https://img.shields.io/travis/adam-paterson/oauth2-slack/master.svg?style=flat-square)](https://travis-ci.org/adam-paterson/oauth2-slack)
[![HHVM Status](https://img.shields.io/hhvm/adam-paterson/oauth2-slack.svg?style=flat-square)](http://hhvm.h4cc.de/package/adam-paterson/oauth2-slack)
[![Coverage Status](https://img.shields.io/scrutinizer/coverage/g/adam-paterson/oauth2-slack.svg?style=flat-square)](https://scrutinizer-ci.com/g/adam-paterson/oauth2-slack/code-structure)
[![Quality Score](https://img.shields.io/scrutinizer/g/adam-paterson/oauth2-slack.svg?style=flat-square)](https://scrutinizer-ci.com/g/adam-paterson/oauth2-slack)
[![Dependency Status](https://img.shields.io/versioneye/d/php/adam-paterson:oauth2-slack/1.1.2.svg?style=flat-square)](https://www.versioneye.com/php/adam-paterson:oauth2-slack/1.1.2)
[![Total Downloads](https://img.shields.io/packagist/dt/adam-paterson/oauth2-slack.svg?style=flat-square)](https://packagist.org/packages/adam-paterson/oauth2-slack)
This package provides Slack OAuth 2.0 support for the PHP League's [OAuth 2.0 Client](https://github.com/thephpleague/oauth2-client).
## Installation
To install, use composer:
```
composer require adam-paterson/oauth2-slack
```
## Usage
Usage is the same as The League's OAuth client, using `\AdamPaterson\OAuth2\Client\Provider\Slack` as the provider.
### Authorization Code Flow
<?php
session_start();
$provider = new \AdamPaterson\OAuth2\Client\Provider\Slack([
'clientId' => '{slack-client-id}',
'clientSecret' => '{slack-client-secret}',
'redirectUri' => 'https://example.com/callback-url',
]);
if (!isset($_GET['code'])) {
// If we don't have an authorization code then get one
$authUrl = $provider->getAuthorizationUrl();
$_SESSION['oauth2state'] = $provider->getState();
header('Location: '.$authUrl);
exit;
// Check given state against previously stored one to mitigate CSRF attack
} elseif (empty($_GET['state']) || ($_GET['state'] !== $_SESSION['oauth2state'])) {
unset($_SESSION['oauth2state']);
exit('Invalid state');
} else {
// Try to get an access token (using the authorization code grant)
$token = $provider->getAccessToken('authorization_code', [
'code' => $_GET['code']
]);
// Optional: Now you have a token you can look up a users profile data
try {
// We got an access token, let's now get the user's details
$team = $provider->getResourceOwner($token);
// Use these details to create a new profile
printf('Hello %s!', $team->getName());
} catch (Exception $e) {
// Failed to get user details
exit('Oh dear...');
}
// Use this to interact with an API on the users behalf
echo $token->getToken();
}
## Testing
``` bash
$ ./vendor/bin/phpunit
```
## Contributing
Please see [CONTRIBUTING](https://github.com/adam-paterson/oauth2-slack/blob/master/CONTRIBUTING.md) for details.
## Credits
- [Adam Paterson](https://github.com/adam-paterson)
- [All Contributors](https://github.com/adam-paterson/oauth2-slack/contributors)
## License
The MIT License (MIT). Please see [License File](https://github.com/adam-paterson/oauth2-slack/blob/master/LICENSE) for more information.

View File

@ -0,0 +1,42 @@
{
"name": "adam-paterson/oauth2-slack",
"description": "Slack OAuth 2.0 Client Provider for The PHP League OAuth2-Client",
"keywords": [
"oauth",
"oauth2",
"authorization",
"authentication",
"idp",
"identity",
"sso",
"single sign on",
"slack",
"slack api"
],
"license": "MIT",
"authors": [
{
"name": "Adam Paterson",
"email": "hello@adampaterson.co.uk"
}
],
"require": {
"php": ">=5.6.0",
"league/oauth2-client": "1.*|2.*"
},
"require-dev": {
"mockery/mockery": "~0.9",
"squizlabs/php_codesniffer": "~2.0",
"phpunit/phpunit": "5.6"
},
"autoload": {
"psr-4": {
"AdamPaterson\\OAuth2\\Client\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"AdamPaterson\\OAuth2\\Client\\Test\\": "test/src/"
}
}
}

View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
bootstrap="vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
>
<logging>
<log type="coverage-html"
target="./build/coverage/html"
charset="UTF-8"
highlight="false"
lowUpperBound="35"
highLowerBound="70"/>
<log type="coverage-clover"
target="./build/coverage/log/coverage.xml"/>
</logging>
<testsuites>
<testsuite name="Package Test Suite">
<directory suffix=".php">./test/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">./</directory>
<exclude>
<directory suffix=".php">./vendor</directory>
<directory suffix=".php">./test</directory>
</exclude>
</whitelist>
</filter>
</phpunit>

View File

@ -0,0 +1,147 @@
<?php
namespace AdamPaterson\OAuth2\Client\Provider;
use League\OAuth2\Client\Provider\AbstractProvider;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use League\OAuth2\Client\Token\AccessToken;
use Psr\Http\Message\ResponseInterface;
/**
* Class Slack
*
* @author Adam Paterson <hello@adampaterson.co.uk>
*
* @package AdamPaterson\OAuth2\Client\Provider
*/
class Slack extends AbstractProvider
{
/**
* Returns the base URL for authorizing a client.
*
* @return string
*/
public function getBaseAuthorizationUrl()
{
return "https://slack.com/oauth/authorize";
}
/**
* Returns the base URL for requesting an access token.
*
* @param array $params
*
* @return string
*/
public function getBaseAccessTokenUrl(array $params)
{
return "https://slack.com/api/oauth.access";
}
/**
* Returns the URL for requesting the resource owner's details.
*
* @param AccessToken $token
*
* @return string
*/
public function getResourceOwnerDetailsUrl(AccessToken $token)
{
$authorizedUser = $this->getAuthorizedUser($token);
$params = [
'token' => $token->getToken(),
'user' => $authorizedUser->getId()
];
$url = 'https://slack.com/api/users.info?'.http_build_query($params);
return $url;
}
/**
* @param $token
*
* @return string
*/
public function getAuthorizedUserTestUrl($token)
{
return "https://slack.com/api/auth.test?token=".$token;
}
/**
* Checks a provider response for errors.
*
* @throws IdentityProviderException
*
* @param ResponseInterface $response
* @param array|string $data Parsed response data
*
* @return void
*/
protected function checkResponse(ResponseInterface $response, $data)
{
}
/**
* Create new resources owner using the generated access token.
*
* @param array $response
* @param AccessToken $token
*
* @return SlackResourceOwner
*/
protected function createResourceOwner(array $response, AccessToken $token)
{
return new SlackResourceOwner($response);
}
/**
* @return array
*/
protected function getDefaultScopes()
{
return [];
}
/**
* @param AccessToken $token
*
* @return mixed
*/
public function fetchAuthorizedUserDetails(AccessToken $token)
{
$url = $this->getAuthorizedUserTestUrl($token);
$request = $this->getAuthenticatedRequest(self::METHOD_GET, $url, $token);
// Keep compatibility with League\OAuth2\Client v1
if (!method_exists($this, 'getParsedResponse')) {
return $this->getResponse($request);
}
return $this->getParsedResponse($request);
}
/**
* @param AccessToken $token
*
* @return SlackAuthorizedUser
*/
public function getAuthorizedUser(AccessToken $token)
{
$response = $this->fetchAuthorizedUserDetails($token);
return $this->createAuthorizedUser($response);
}
/**
* @param $response
*
* @return SlackAuthorizedUser
*/
protected function createAuthorizedUser($response)
{
return new SlackAuthorizedUser($response);
}
}

View File

@ -0,0 +1,66 @@
<?php
namespace AdamPaterson\OAuth2\Client\Provider;
use League\OAuth2\Client\Provider\ResourceOwnerInterface;
class SlackAuthorizedUser implements ResourceOwnerInterface
{
protected $response;
/**
* SlackAuthorizedUser constructor.
*
* @param $response
*/
public function __construct(array $response)
{
$this->response = $response;
}
/**
* Returns the identifier of the authorized resource owner.
*
* @return mixed
*/
public function getId()
{
return $this->response['user_id'];
}
/**
* Return all of the owner details available as an array.
*
* @return array
*/
public function toArray()
{
return $this->response;
}
public function getUrl()
{
return $this->response['url'] ?: null;
}
public function getTeam()
{
return $this->response['team'] ?: null;
}
public function getUser()
{
return $this->response['user'] ?: null;
}
public function getTeamId()
{
return $this->response['team_id'] ?: null;
}
public function getUserId()
{
return $this->response['user_id'] ?: null;
}
}

View File

@ -0,0 +1,134 @@
<?php
namespace AdamPaterson\OAuth2\Client\Provider;
use League\OAuth2\Client\Provider\ResourceOwnerInterface;
/**
* Class SlackResourceOwner
*
* @author Adam Paterson <hello@adampaterson.co.uk>
*
* @package AdamPaterson\OAuth2\Client\Provider
*/
class SlackResourceOwner implements ResourceOwnerInterface
{
protected $response;
public function __construct(array $response)
{
$this->response = $response;
}
/**
* Return all of the owner details available as an array.
*
* @return array
*/
public function toArray()
{
return $this->response;
}
public function getId()
{
return $this->response['user']['id'] ?: null;
}
public function getName()
{
return $this->response['user']['name'] ?: null;
}
public function isDeleted()
{
return $this->response['user']['deleted'] ?: null;
}
public function getColor()
{
return $this->response['user']['color'] ?: null;
}
public function getProfile()
{
return $this->response['user']['profile'] ?: null;
}
public function getFirstName()
{
return $this->response['user']['profile']['first_name'] ?: null;
}
public function getLastName()
{
return $this->response['user']['profile']['last_name'] ?: null;
}
public function getRealName()
{
return $this->response['user']['profile']['real_name'] ?: null;
}
public function getEmail()
{
return $this->response['user']['profile']['email'] ?: null;
}
public function getSkype()
{
return $this->response['user']['profile']['skype'] ?: null;
}
public function getPhone()
{
return $this->response['user']['profile']['phone'] ?: null;
}
public function getImage24()
{
return $this->response['user']['profile']['image_24'] ?: null;
}
public function getImage32()
{
return $this->response['user']['profile']['image_32'] ?: null;
}
public function getImage48()
{
return $this->response['user']['profile']['image_48'] ?: null;
}
public function getImage72()
{
return $this->response['user']['profile']['image_72'] ?: null;
}
public function getImage192()
{
return $this->response['user']['profile']['image_192'] ?: null;
}
public function isAdmin()
{
return $this->response['user']['is_admin'] ?: null;
}
public function isOwner()
{
return $this->response['user']['is_owner'] ?: null;
}
public function hasTwoFactorAuthentication()
{
return $this->response['user']['has_2fa'] ?: null;
}
public function hasFiles()
{
return $this->response['user']['has_files'] ?: null;
}
}

View File

@ -0,0 +1,269 @@
<?php
namespace AdamPaterson\OAuth2\Client\Test\Provider;
use AdamPaterson\OAuth2\Client\Provider\Slack;
use Mockery as m;
use ReflectionClass;
class SlackTest extends \PHPUnit_Framework_TestCase
{
protected $provider;
protected static function getMethod($name)
{
$class = new ReflectionClass('AdamPaterson\OAuth2\Client\Provider\Slack');
$method = $class->getMethod($name);
$method->setAccessible(true);
return $method;
}
protected function setUp()
{
$this->provider = new Slack([
'clientId' => 'mock_client_id',
'clientSecret' => 'mock_secret',
'redirectUri' => 'none',
]);
}
public function tearDown()
{
m::close();
parent::tearDown();
}
public function testAuthorizationUrl()
{
$url = $this->provider->getAuthorizationUrl();
$uri = parse_url($url);
parse_str($uri['query'], $query);
$this->assertArrayHasKey('client_id', $query);
$this->assertArrayHasKey('redirect_uri', $query);
$this->assertArrayHasKey('state', $query);
$this->assertArrayHasKey('scope', $query);
$this->assertArrayHasKey('response_type', $query);
$this->assertArrayHasKey('approval_prompt', $query);
$this->assertNotNull($this->provider->getState());
}
public function testGetResourceOwnerDetailsUrl()
{
$authUser = json_decode('{"ok": true,"url": "https:\/\/myteam.slack.com\/","team": "My Team","user": "cal","team_id": "T12345","user_id": "U12345"}',true);
$token = m::mock('League\OAuth2\Client\Token\AccessToken', [['access_token' => 'mock_access_token']]);
$token->shouldReceive('__toString')->andReturn('mock_access_token');
$provider = m::mock('AdamPaterson\OAuth2\Client\Provider\Slack');
$provider->shouldReceive('getAuthorizedUser')->andReturn($authUser);
$provider->shouldReceive('getResourceOwnerDetailsUrl')->once()->andReturn('https://slack.com/api/users.info?token=mock_access_token&user=U12345');
$url = $provider->getResourceOwnerDetailsUrl($token);
$uri = parse_url($url);
$this->assertEquals('/api/users.info', $uri['path']);
$this->assertEquals('token=mock_access_token&user=U12345', $uri['query']);
}
public function testGetAuthorizationUrl()
{
$params = [];
$url = $this->provider->getAuthorizationUrl($params);
$uri = parse_url($url);
$this->assertEquals('/oauth/authorize', $uri['path']);
}
public function testGetBaseAccessTokenUrl()
{
$params = [];
$url = $this->provider->getBaseAccessTokenUrl($params);
$uri = parse_url($url);
$this->assertEquals('/api/oauth.access', $uri['path']);
}
public function testGetAccessToken()
{
$response = m::mock('Psr\Http\Message\ResponseInterface');
$response->shouldReceive('getBody')->andReturn('{"access_token": "mock_access_token", "expires_in": 3600}');
$response->shouldReceive('getHeader')->andReturn(['content-type' => 'json']);
$client = m::mock('GuzzleHttp\ClientInterface');
$client->shouldReceive('send')->times(1)->andReturn($response);
$this->provider->setHttpClient($client);
$token = $this->provider->getAccessToken('authorization_code', ['code' => 'mock_authorization_code']);
$this->assertEquals('mock_access_token', $token->getToken());
$this->assertLessThanOrEqual(time() + 3600, $token->getExpires());
$this->assertGreaterThanOrEqual(time(), $token->getExpires());
$this->assertNull($token->getRefreshToken());
$this->assertNull($token->getResourceOwnerId());
}
public function testCheckResponseThrowsIdentityProviderException()
{
$method = self::getMethod('checkResponse');
$responseInterface = m::mock('Psr\Http\Message\ResponseInterface');
$data = ['ok' => false];
try {
$method->invoke($this->provider, $responseInterface, $data);
} catch (\Exception $e) {
$this->assertEquals(400, $e->getCode());
$this->assertEquals("Unknown error", $e->getMessage());
}
}
public function testGetAuthorizedUserTestUrl()
{
$token = m::mock('League\OAuth2\Client\Token\AccessToken', [['access_token' => 'mock_access_token']]);
$token->shouldReceive('__toString')->andReturn('mock_access_token');
$url = $this->provider->getAuthorizedUserTestUrl($token);
$uri = parse_url($url);
$this->assertEquals('/api/auth.test', $uri['path']);
$this->assertEquals('token=mock_access_token', $uri['query']);
}
public function testGetAuthorizedUserDetails()
{
$url = uniqid();
$team = uniqid();
$userName = uniqid();
$teamId = uniqid();
$userId = uniqid();
$postResponse = m::mock('Psr\Http\Message\ResponseInterface');
$postResponse->shouldReceive('getBody')->andReturn('access_token=mock_access_token&expires=3600&refresh_token=mock_refresh_token&otherKey={1234}');
$postResponse->shouldReceive('getHeader')->andReturn(['content-type' => 'application/x-www-form-urlencoded']);
$postResponse->shouldReceive('getStatusCode')->andReturn(200);
$userResponse = m::mock('Psr\Http\Message\ResponseInterface');
$userResponse->shouldReceive('getBody')->andReturn('{"ok": true,"url": "'.$url.'","user": "'.$userName.'","team": "'.$team.'","team_id": "'.$teamId.'","user_id": "'.$userId.'"}');
$userResponse->shouldReceive('getHeader')->andReturn(['content-type' => 'json']);
$userResponse->shouldReceive('getStatusCode')->andReturn(200);
$client = m::mock('GuzzleHttp\ClientInterface');
$client->shouldReceive('send')
->times(2)
->andReturn($postResponse, $userResponse);
$this->provider->setHttpClient($client);
$token = $this->provider->getAccessToken('authorization_code', ['code' => 'mock_authorization_code']);
$user = $this->provider->getAuthorizedUser($token);
$this->assertEquals($userId, $user->getId());
$this->assertEquals($url, $user->getUrl());
$this->assertEquals($url, $user->toArray()['url']);
$this->assertEquals($team, $user->getTeam());
$this->assertEquals($team, $user->toArray()['team']);
$this->assertEquals($userName, $user->getUser());
$this->assertEquals($userName, $user->toArray()['user']);
$this->assertEquals($teamId, $user->getTeamId());
$this->assertEquals($teamId, $user->toArray()['team_id']);
$this->assertEquals($userId, $user->getUserId());
$this->assertEquals($userId, $user->toArray()['user_id']);
}
public function testGetResourceOwnerDetails()
{
$id = uniqid();
$name = uniqid();
$deleted = false;
$color = uniqid();
$profile = [
"first_name" => uniqid(),
"last_name" => uniqid(),
"real_name" => uniqid(),
"email" => uniqid(),
"skype" => uniqid(),
"phone" => uniqid(),
"image_24" => uniqid(),
"image_32" => uniqid(),
"image_48" => uniqid(),
"image_72" => uniqid(),
"image_192" => uniqid()
];
$isAdmin = true;
$isOwner = true;
$has2FA = true;
$hasFiles = true;
$url = uniqid();
$userName = uniqid();
$team = uniqid();
$teamId = uniqid();
$userId = uniqid();
$accessTokenResponse = m::mock('Psr\Http\Message\ResponseInterface');
$accessTokenResponse->shouldReceive('getBody')->andReturn('access_token=mock_access_token&expires=3600&refresh_token=mock_refresh_token&otherKey={1234}');
$accessTokenResponse->shouldReceive('getHeader')->andReturn(['content-type' => 'application/x-www-form-urlencoded']);
$accessTokenResponse->shouldReceive('getStatusCode')->andReturn(200);
$authUser = m::mock('Psr\Http\Message\ResponseInterface');
$authUser->shouldReceive('getBody')->andReturn('{"ok": true,"url": "'.$url.'","user": "'.$userName.'","team": "'.$team.'","team_id": "'.$teamId.'","user_id": "'.$userId.'"}');
$authUser->shouldReceive('getHeader')->andReturn(['content-type' => 'json']);
$authUser->shouldReceive('getStatusCode')->andReturn(200);
$authUserResponse = m::mock('Psr\Http\Message\ResponseInterface');
$authUserResponse->shouldReceive('getBody')->andReturn('{"ok": true,"url": "'.$url.'","team": "'.$team.'","user": "'.$userName.'","team_id": "'.$teamId.'","user_id": "'.$userId.'"}');
$authUserResponse->shouldReceive('getHeader')->andReturn(['content-type' => 'json']);
$authUserResponse->shouldReceive('getStatusCode')->andReturn(200);
$userResponse = m::mock('Psr\Http\Message\ResponseInterface');
$userResponse->shouldReceive('getBody')->andReturn('{"ok": true,"user": {"id": "'.$id.'","name": "'.$name.'","deleted": false,"color": "'.$color.'","profile": {"first_name": "'.$profile["first_name"].'","last_name": "'.$profile["last_name"].'","real_name": "'.$profile["real_name"].'","email": "'.$profile["email"].'","skype": "'.$profile["skype"].'","phone": "'.$profile["phone"].'","image_24": "'.$profile["image_24"].'","image_32": "'.$profile["image_32"].'","image_48": "'.$profile["image_48"].'","image_72": "'.$profile["image_72"].'","image_192": "'.$profile["image_192"].'"},"is_admin": true,"is_owner": true,"has_2fa": true,"has_files": true}}');
$userResponse->shouldReceive('getHeader')->andReturn(['content-type' => 'json']);
$userResponse->shouldReceive('getStatusCode')->andReturn(200);
$client = m::mock('GuzzleHttp\ClientInterface');
$client->shouldReceive('send')
->times(3)
->andReturn($accessTokenResponse, $authUserResponse, $userResponse);
$this->provider->setHttpClient($client);
$token = $this->provider->getAccessToken('authorization_code', ['code' => 'mock_authorization_code']);
$user = $this->provider->getResourceOwner($token);
$this->assertEquals($id, $user->getId());
$this->assertEquals($id, $user->toArray()['user']['id']);
$this->assertEquals($name, $user->getName());
$this->assertEquals($name, $user->toArray()['user']['name']);
$this->assertEquals($deleted, $user->isDeleted());
$this->assertEquals($deleted, $user->toArray()['user']['deleted']);
$this->assertEquals($color, $user->getColor());
$this->assertEquals($color, $user->toArray()['user']['color']);
$this->assertEquals($profile, $user->getProfile());
$this->assertEquals($profile, $user->toArray()['user']['profile']);
$this->assertEquals($profile['first_name'], $user->getFirstName());
$this->assertEquals($profile['first_name'], $user->toArray()['user']['profile']['first_name']);
$this->assertEquals($profile['last_name'], $user->getLastName());
$this->assertEquals($profile['last_name'], $user->toArray()['user']['profile']['last_name']);
$this->assertEquals($profile['real_name'], $user->getRealName());
$this->assertEquals($profile['real_name'], $user->toArray()['user']['profile']['real_name']);
$this->assertEquals($profile['email'], $user->getEmail());
$this->assertEquals($profile['email'], $user->toArray()['user']['profile']['email']);
$this->assertEquals($profile['skype'], $user->getSkype());
$this->assertEquals($profile['skype'], $user->toArray()['user']['profile']['skype']);
$this->assertEquals($profile['phone'], $user->getPhone());
$this->assertEquals($profile['phone'], $user->toArray()['user']['profile']['phone']);
$this->assertEquals($profile['image_24'], $user->getImage24());
$this->assertEquals($profile['image_24'], $user->toArray()['user']['profile']['image_24']);
$this->assertEquals($profile['image_32'], $user->getImage32());
$this->assertEquals($profile['image_32'], $user->toArray()['user']['profile']['image_32']);
$this->assertEquals($profile['image_48'], $user->getImage48());
$this->assertEquals($profile['image_48'], $user->toArray()['user']['profile']['image_48']);
$this->assertEquals($profile['image_72'], $user->getImage72());
$this->assertEquals($profile['image_72'], $user->toArray()['user']['profile']['image_72']);
$this->assertEquals($profile['image_192'], $user->getImage192());
$this->assertEquals($profile['image_192'], $user->toArray()['user']['profile']['image_192']);
$this->assertEquals($isAdmin, $user->isAdmin());
$this->assertEquals($isAdmin, $user->toArray()['user']['is_admin']);
$this->assertEquals($isOwner, $user->isOwner());
$this->assertEquals($isOwner, $user->toArray()['user']['is_owner']);
$this->assertEquals($has2FA, $user->hasTwoFactorAuthentication());
$this->assertEquals($has2FA, $user->toArray()['user']['has_2fa']);
$this->assertEquals($hasFiles, $user->hasFiles());
$this->assertEquals($hasFiles, $user->toArray()['user']['has_files']);
}
}

View File

@ -0,0 +1,7 @@
<?php
// autoload.php @generated by Composer
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInitbd5d628261f1599470d200425791c144::getLoader();

View File

@ -0,0 +1,572 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Autoload;
/**
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
*
* $loader = new \Composer\Autoload\ClassLoader();
*
* // register classes with namespaces
* $loader->add('Symfony\Component', __DIR__.'/component');
* $loader->add('Symfony', __DIR__.'/framework');
*
* // activate the autoloader
* $loader->register();
*
* // to enable searching the include path (eg. for PEAR packages)
* $loader->setUseIncludePath(true);
*
* In this example, if you try to use a class in the Symfony\Component
* namespace or one of its children (Symfony\Component\Console for instance),
* the autoloader will first look for the class under the component/
* directory, and it will then fallback to the framework/ directory if not
* found before giving up.
*
* This class is loosely based on the Symfony UniversalClassLoader.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @see https://www.php-fig.org/psr/psr-0/
* @see https://www.php-fig.org/psr/psr-4/
*/
class ClassLoader
{
/** @var ?string */
private $vendorDir;
// PSR-4
/**
* @var array[]
* @psalm-var array<string, array<string, int>>
*/
private $prefixLengthsPsr4 = array();
/**
* @var array[]
* @psalm-var array<string, array<int, string>>
*/
private $prefixDirsPsr4 = array();
/**
* @var array[]
* @psalm-var array<string, string>
*/
private $fallbackDirsPsr4 = array();
// PSR-0
/**
* @var array[]
* @psalm-var array<string, array<string, string[]>>
*/
private $prefixesPsr0 = array();
/**
* @var array[]
* @psalm-var array<string, string>
*/
private $fallbackDirsPsr0 = array();
/** @var bool */
private $useIncludePath = false;
/**
* @var string[]
* @psalm-var array<string, string>
*/
private $classMap = array();
/** @var bool */
private $classMapAuthoritative = false;
/**
* @var bool[]
* @psalm-var array<string, bool>
*/
private $missingClasses = array();
/** @var ?string */
private $apcuPrefix;
/**
* @var self[]
*/
private static $registeredLoaders = array();
/**
* @param ?string $vendorDir
*/
public function __construct($vendorDir = null)
{
$this->vendorDir = $vendorDir;
}
/**
* @return string[]
*/
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
}
return array();
}
/**
* @return array[]
* @psalm-return array<string, array<int, string>>
*/
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
/**
* @return array[]
* @psalm-return array<string, string>
*/
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
/**
* @return array[]
* @psalm-return array<string, string>
*/
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
/**
* @return string[] Array of classname => path
* @psalm-return array<string, string>
*/
public function getClassMap()
{
return $this->classMap;
}
/**
* @param string[] $classMap Class to filename map
* @psalm-param array<string, string> $classMap
*
* @return void
*/
public function addClassMap(array $classMap)
{
if ($this->classMap) {
$this->classMap = array_merge($this->classMap, $classMap);
} else {
$this->classMap = $classMap;
}
}
/**
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
*
* @param string $prefix The prefix
* @param string[]|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
*
* @return void
*/
public function add($prefix, $paths, $prepend = false)
{
if (!$prefix) {
if ($prepend) {
$this->fallbackDirsPsr0 = array_merge(
(array) $paths,
$this->fallbackDirsPsr0
);
} else {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0,
(array) $paths
);
}
return;
}
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
return;
}
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
(array) $paths,
$this->prefixesPsr0[$first][$prefix]
);
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param string[]|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
$this->fallbackDirsPsr4 = array_merge(
(array) $paths,
$this->fallbackDirsPsr4
);
} else {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4,
(array) $paths
);
}
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
// Register directories for a new namespace.
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
(array) $paths,
$this->prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
*
* @param string $prefix The prefix
* @param string[]|string $paths The PSR-0 base directories
*
* @return void
*/
public function set($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr0 = (array) $paths;
} else {
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
}
}
/**
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param string[]|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function setPsr4($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr4 = (array) $paths;
} else {
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
}
}
/**
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*
* @return void
*/
public function setUseIncludePath($useIncludePath)
{
$this->useIncludePath = $useIncludePath;
}
/**
* Can be used to check if the autoloader uses the include path to check
* for classes.
*
* @return bool
*/
public function getUseIncludePath()
{
return $this->useIncludePath;
}
/**
* Turns off searching the prefix and fallback directories for classes
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*
* @return void
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
$this->classMapAuthoritative = $classMapAuthoritative;
}
/**
* Should class lookup fail if not found in the current class map?
*
* @return bool
*/
public function isClassMapAuthoritative()
{
return $this->classMapAuthoritative;
}
/**
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
*
* @param string|null $apcuPrefix
*
* @return void
*/
public function setApcuPrefix($apcuPrefix)
{
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
}
/**
* The APCu prefix in use, or null if APCu caching is not enabled.
*
* @return string|null
*/
public function getApcuPrefix()
{
return $this->apcuPrefix;
}
/**
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*
* @return void
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
if (null === $this->vendorDir) {
return;
}
if ($prepend) {
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
} else {
unset(self::$registeredLoaders[$this->vendorDir]);
self::$registeredLoaders[$this->vendorDir] = $this;
}
}
/**
* Unregisters this instance as an autoloader.
*
* @return void
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
if (null !== $this->vendorDir) {
unset(self::$registeredLoaders[$this->vendorDir]);
}
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return true|null True if loaded, null otherwise
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
includeFile($file);
return true;
}
return null;
}
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|false The path if found, false otherwise
*/
public function findFile($class)
{
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
return false;
}
if (null !== $this->apcuPrefix) {
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
if ($hit) {
return $file;
}
}
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if (false === $file && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if (null !== $this->apcuPrefix) {
apcu_add($this->apcuPrefix.$class, $file);
}
if (false === $file) {
// Remember that this class does not exist.
$this->missingClasses[$class] = true;
}
return $file;
}
/**
* Returns the currently registered loaders indexed by their corresponding vendor directories.
*
* @return self[]
*/
public static function getRegisteredLoaders()
{
return self::$registeredLoaders;
}
/**
* @param string $class
* @param string $ext
* @return string|false
*/
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath . '\\';
if (isset($this->prefixDirsPsr4[$search])) {
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach ($this->prefixDirsPsr4[$search] as $dir) {
if (file_exists($file = $dir . $pathEnd)) {
return $file;
}
}
}
}
}
// PSR-4 fallback dirs
foreach ($this->fallbackDirsPsr4 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
// PSR-0 lookup
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else {
// PEAR-like class name
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
}
if (isset($this->prefixesPsr0[$first])) {
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) {
foreach ($dirs as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
}
}
}
// PSR-0 fallback dirs
foreach ($this->fallbackDirsPsr0 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
// PSR-0 include paths.
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
return $file;
}
return false;
}
}
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*
* @param string $file
* @return void
* @private
*/
function includeFile($file)
{
include $file;
}

View File

@ -0,0 +1,350 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer;
use Composer\Autoload\ClassLoader;
use Composer\Semver\VersionParser;
/**
* This class is copied in every Composer installed project and available to all
*
* See also https://getcomposer.org/doc/07-runtime.md#installed-versions
*
* To require its presence, you can require `composer-runtime-api ^2.0`
*/
class InstalledVersions
{
/**
* @var mixed[]|null
* @psalm-var array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}|array{}|null
*/
private static $installed;
/**
* @var bool|null
*/
private static $canGetVendors;
/**
* @var array[]
* @psalm-var array<string, array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}>
*/
private static $installedByVendor = array();
/**
* Returns a list of all package names which are present, either by being installed, replaced or provided
*
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackages()
{
$packages = array();
foreach (self::getInstalled() as $installed) {
$packages[] = array_keys($installed['versions']);
}
if (1 === \count($packages)) {
return $packages[0];
}
return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
}
/**
* Returns a list of all package names with a specific type e.g. 'library'
*
* @param string $type
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackagesByType($type)
{
$packagesByType = array();
foreach (self::getInstalled() as $installed) {
foreach ($installed['versions'] as $name => $package) {
if (isset($package['type']) && $package['type'] === $type) {
$packagesByType[] = $name;
}
}
}
return $packagesByType;
}
/**
* Checks whether the given package is installed
*
* This also returns true if the package name is provided or replaced by another package
*
* @param string $packageName
* @param bool $includeDevRequirements
* @return bool
*/
public static function isInstalled($packageName, $includeDevRequirements = true)
{
foreach (self::getInstalled() as $installed) {
if (isset($installed['versions'][$packageName])) {
return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']);
}
}
return false;
}
/**
* Checks whether the given package satisfies a version constraint
*
* e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
*
* Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
*
* @param VersionParser $parser Install composer/semver to have access to this class and functionality
* @param string $packageName
* @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
* @return bool
*/
public static function satisfies(VersionParser $parser, $packageName, $constraint)
{
$constraint = $parser->parseConstraints($constraint);
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
return $provided->matches($constraint);
}
/**
* Returns a version constraint representing all the range(s) which are installed for a given package
*
* It is easier to use this via isInstalled() with the $constraint argument if you need to check
* whether a given version of a package is installed, and not just whether it exists
*
* @param string $packageName
* @return string Version constraint usable with composer/semver
*/
public static function getVersionRanges($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
$ranges = array();
if (isset($installed['versions'][$packageName]['pretty_version'])) {
$ranges[] = $installed['versions'][$packageName]['pretty_version'];
}
if (array_key_exists('aliases', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
}
if (array_key_exists('replaced', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
}
if (array_key_exists('provided', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
}
return implode(' || ', $ranges);
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['version'])) {
return null;
}
return $installed['versions'][$packageName]['version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getPrettyVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['pretty_version'])) {
return null;
}
return $installed['versions'][$packageName]['pretty_version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
*/
public static function getReference($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['reference'])) {
return null;
}
return $installed['versions'][$packageName]['reference'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
*/
public static function getInstallPath($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @return array
* @psalm-return array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}
*/
public static function getRootPackage()
{
$installed = self::getInstalled();
return $installed[0]['root'];
}
/**
* Returns the raw installed.php data for custom implementations
*
* @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
* @return array[]
* @psalm-return array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}
*/
public static function getRawData()
{
@trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
self::$installed = include __DIR__ . '/installed.php';
} else {
self::$installed = array();
}
}
return self::$installed;
}
/**
* Returns the raw data of all installed.php which are currently loaded for custom implementations
*
* @return array[]
* @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}>
*/
public static function getAllRawData()
{
return self::getInstalled();
}
/**
* Lets you reload the static array from another file
*
* This is only useful for complex integrations in which a project needs to use
* this class but then also needs to execute another project's autoloader in process,
* and wants to ensure both projects have access to their version of installed.php.
*
* A typical case would be PHPUnit, where it would need to make sure it reads all
* the data it needs from this class, then call reload() with
* `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
* the project in which it runs can then also use this class safely, without
* interference between PHPUnit's dependencies and the project's dependencies.
*
* @param array[] $data A vendor/composer/installed.php data set
* @return void
*
* @psalm-param array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>} $data
*/
public static function reload($data)
{
self::$installed = $data;
self::$installedByVendor = array();
}
/**
* @return array[]
* @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}>
*/
private static function getInstalled()
{
if (null === self::$canGetVendors) {
self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
}
$installed = array();
if (self::$canGetVendors) {
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
if (isset(self::$installedByVendor[$vendorDir])) {
$installed[] = self::$installedByVendor[$vendorDir];
} elseif (is_file($vendorDir.'/composer/installed.php')) {
$installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php';
if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
self::$installed = $installed[count($installed) - 1];
}
}
}
}
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
self::$installed = require __DIR__ . '/installed.php';
} else {
self::$installed = array();
}
}
$installed[] = self::$installed;
return $installed;
}
}

View File

@ -0,0 +1,21 @@
Copyright (c) Nils Adermann, Jordi Boggiano
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,10 @@
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
);

View File

@ -0,0 +1,9 @@
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
);

View File

@ -0,0 +1,20 @@
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'Wohali\\OAuth2\\Client\\' => array($vendorDir . '/wohali/oauth2-discord-new/src'),
'Vertisan\\OAuth2\\Client\\Provider\\' => array($vendorDir . '/vertisan/oauth2-twitch-helix/src'),
'TheNetworg\\OAuth2\\Client\\' => array($vendorDir . '/thenetworg/oauth2-azure/src'),
'Stevenmaguire\\OAuth2\\Client\\' => array($vendorDir . '/stevenmaguire/oauth2-keycloak/src'),
'Omines\\OAuth2\\Client\\' => array($vendorDir . '/omines/oauth2-gitlab/src'),
'Mrjoops\\OAuth2\\Client\\' => array($vendorDir . '/mrjoops/oauth2-jira/src'),
'Gravure\\Patreon\\Oauth\\' => array($vendorDir . '/gravure/oauth2-patreon/src'),
'Grav\\Plugin\\Login\\OAuth2\\' => array($baseDir . '/classes'),
'Foxworth42\\OAuth2\\Client\\' => array($vendorDir . '/foxworth42/oauth2-okta/src'),
'Firebase\\JWT\\' => array($vendorDir . '/firebase/php-jwt/src'),
'AdamPaterson\\OAuth2\\Client\\' => array($vendorDir . '/adam-paterson/oauth2-slack/src'),
);

View File

@ -0,0 +1,57 @@
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInitbd5d628261f1599470d200425791c144
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
/**
* @return \Composer\Autoload\ClassLoader
*/
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
require __DIR__ . '/platform_check.php';
spl_autoload_register(array('ComposerAutoloaderInitbd5d628261f1599470d200425791c144', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
spl_autoload_unregister(array('ComposerAutoloaderInitbd5d628261f1599470d200425791c144', 'loadClassLoader'));
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
if ($useStaticLoader) {
require __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInitbd5d628261f1599470d200425791c144::getInitializer($loader));
} else {
$map = require __DIR__ . '/autoload_namespaces.php';
foreach ($map as $namespace => $path) {
$loader->set($namespace, $path);
}
$map = require __DIR__ . '/autoload_psr4.php';
foreach ($map as $namespace => $path) {
$loader->setPsr4($namespace, $path);
}
$classMap = require __DIR__ . '/autoload_classmap.php';
if ($classMap) {
$loader->addClassMap($classMap);
}
}
$loader->register(true);
return $loader;
}
}

View File

@ -0,0 +1,110 @@
<?php
// autoload_static.php @generated by Composer
namespace Composer\Autoload;
class ComposerStaticInitbd5d628261f1599470d200425791c144
{
public static $prefixLengthsPsr4 = array (
'W' =>
array (
'Wohali\\OAuth2\\Client\\' => 21,
),
'V' =>
array (
'Vertisan\\OAuth2\\Client\\Provider\\' => 32,
),
'T' =>
array (
'TheNetworg\\OAuth2\\Client\\' => 25,
),
'S' =>
array (
'Stevenmaguire\\OAuth2\\Client\\' => 28,
),
'O' =>
array (
'Omines\\OAuth2\\Client\\' => 21,
),
'M' =>
array (
'Mrjoops\\OAuth2\\Client\\' => 22,
),
'G' =>
array (
'Gravure\\Patreon\\Oauth\\' => 22,
'Grav\\Plugin\\Login\\OAuth2\\' => 25,
),
'F' =>
array (
'Foxworth42\\OAuth2\\Client\\' => 25,
'Firebase\\JWT\\' => 13,
),
'A' =>
array (
'AdamPaterson\\OAuth2\\Client\\' => 27,
),
);
public static $prefixDirsPsr4 = array (
'Wohali\\OAuth2\\Client\\' =>
array (
0 => __DIR__ . '/..' . '/wohali/oauth2-discord-new/src',
),
'Vertisan\\OAuth2\\Client\\Provider\\' =>
array (
0 => __DIR__ . '/..' . '/vertisan/oauth2-twitch-helix/src',
),
'TheNetworg\\OAuth2\\Client\\' =>
array (
0 => __DIR__ . '/..' . '/thenetworg/oauth2-azure/src',
),
'Stevenmaguire\\OAuth2\\Client\\' =>
array (
0 => __DIR__ . '/..' . '/stevenmaguire/oauth2-keycloak/src',
),
'Omines\\OAuth2\\Client\\' =>
array (
0 => __DIR__ . '/..' . '/omines/oauth2-gitlab/src',
),
'Mrjoops\\OAuth2\\Client\\' =>
array (
0 => __DIR__ . '/..' . '/mrjoops/oauth2-jira/src',
),
'Gravure\\Patreon\\Oauth\\' =>
array (
0 => __DIR__ . '/..' . '/gravure/oauth2-patreon/src',
),
'Grav\\Plugin\\Login\\OAuth2\\' =>
array (
0 => __DIR__ . '/../..' . '/classes',
),
'Foxworth42\\OAuth2\\Client\\' =>
array (
0 => __DIR__ . '/..' . '/foxworth42/oauth2-okta/src',
),
'Firebase\\JWT\\' =>
array (
0 => __DIR__ . '/..' . '/firebase/php-jwt/src',
),
'AdamPaterson\\OAuth2\\Client\\' =>
array (
0 => __DIR__ . '/..' . '/adam-paterson/oauth2-slack/src',
),
);
public static $classMap = array (
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInitbd5d628261f1599470d200425791c144::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInitbd5d628261f1599470d200425791c144::$prefixDirsPsr4;
$loader->classMap = ComposerStaticInitbd5d628261f1599470d200425791c144::$classMap;
}, null, ClassLoader::class);
}
}

View File

@ -0,0 +1,608 @@
{
"packages": [
{
"name": "adam-paterson/oauth2-slack",
"version": "1.1.3",
"version_normalized": "1.1.3.0",
"source": {
"type": "git",
"url": "https://github.com/adam-paterson/oauth2-slack.git",
"reference": "ccc329eb3036a89d110227a4137e15d4a5661678"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/adam-paterson/oauth2-slack/zipball/ccc329eb3036a89d110227a4137e15d4a5661678",
"reference": "ccc329eb3036a89d110227a4137e15d4a5661678",
"shasum": ""
},
"require": {
"league/oauth2-client": "1.*|2.*",
"php": ">=5.6.0"
},
"require-dev": {
"mockery/mockery": "~0.9",
"phpunit/phpunit": "5.6",
"squizlabs/php_codesniffer": "~2.0"
},
"time": "2017-06-20T14:43:31+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"AdamPaterson\\OAuth2\\Client\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Adam Paterson",
"email": "hello@adampaterson.co.uk"
}
],
"description": "Slack OAuth 2.0 Client Provider for The PHP League OAuth2-Client",
"keywords": [
"Authentication",
"SSO",
"authorization",
"identity",
"idp",
"oauth",
"oauth2",
"single sign on",
"slack",
"slack api"
],
"support": {
"issues": "https://github.com/adam-paterson/oauth2-slack/issues",
"source": "https://github.com/adam-paterson/oauth2-slack/tree/master"
},
"install-path": "../adam-paterson/oauth2-slack"
},
{
"name": "firebase/php-jwt",
"version": "v5.5.1",
"version_normalized": "5.5.1.0",
"source": {
"type": "git",
"url": "https://github.com/firebase/php-jwt.git",
"reference": "83b609028194aa042ea33b5af2d41a7427de80e6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/firebase/php-jwt/zipball/83b609028194aa042ea33b5af2d41a7427de80e6",
"reference": "83b609028194aa042ea33b5af2d41a7427de80e6",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"require-dev": {
"phpunit/phpunit": ">=4.8 <=9"
},
"suggest": {
"paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present"
},
"time": "2021-11-08T20:18:51+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Firebase\\JWT\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Neuman Vong",
"email": "neuman+pear@twilio.com",
"role": "Developer"
},
{
"name": "Anant Narayanan",
"email": "anant@php.net",
"role": "Developer"
}
],
"description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.",
"homepage": "https://github.com/firebase/php-jwt",
"keywords": [
"jwt",
"php"
],
"support": {
"issues": "https://github.com/firebase/php-jwt/issues",
"source": "https://github.com/firebase/php-jwt/tree/v5.5.1"
},
"install-path": "../firebase/php-jwt"
},
{
"name": "foxworth42/oauth2-okta",
"version": "v1.0.2",
"version_normalized": "1.0.2.0",
"source": {
"type": "git",
"url": "https://github.com/foxworth42/oauth2-okta.git",
"reference": "0e7c2eb68f57eff8aafc4a3f0a1a1ec1c147c946"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/foxworth42/oauth2-okta/zipball/0e7c2eb68f57eff8aafc4a3f0a1a1ec1c147c946",
"reference": "0e7c2eb68f57eff8aafc4a3f0a1a1ec1c147c946",
"shasum": ""
},
"require": {
"league/oauth2-client": "^2.0",
"php": ">=7.1.0"
},
"require-dev": {
"php-coveralls/php-coveralls": "^2.1",
"phpunit/phpunit": "^7.0",
"squizlabs/php_codesniffer": "^3.4"
},
"time": "2020-09-28T06:28:26+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Foxworth42\\OAuth2\\Client\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ed Walker",
"email": "github@foxwire.org"
}
],
"description": "Okta OAuth 2.0 Client Provider for The PHP League OAuth2-Client",
"keywords": [
"Authentication",
"authorization",
"client",
"oauth",
"oauth2",
"okta"
],
"support": {
"issues": "https://github.com/foxworth42/oauth2-okta/issues",
"source": "https://github.com/foxworth42/oauth2-okta/tree/v1.0.2"
},
"install-path": "../foxworth42/oauth2-okta"
},
{
"name": "gravure/oauth2-patreon",
"version": "dev-master",
"version_normalized": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/gravure/oauth2-patreon.git",
"reference": "32c5bb7c6cdfb0cbb4e396ca8e9cbde447f41f47"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/gravure/oauth2-patreon/zipball/32c5bb7c6cdfb0cbb4e396ca8e9cbde447f41f47",
"reference": "32c5bb7c6cdfb0cbb4e396ca8e9cbde447f41f47",
"shasum": ""
},
"require": {
"league/oauth2-client": "^1.0 || ^2.0",
"php": "5.6.* || >=7.0"
},
"time": "2018-02-06T12:41:19+00:00",
"default-branch": true,
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Gravure\\Patreon\\Oauth\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Daniël Klabbers",
"email": "daniel@klabbers.email",
"homepage": "http://luceos.com"
}
],
"description": "Provides Patreon OAuth 2.0 support for PHP League's OAuth 2.0 Client.",
"keywords": [
"league",
"oauth2",
"patreon"
],
"support": {
"issues": "https://github.com/gravure/oauth2-patreon/issues",
"source": "https://github.com/gravure/oauth2-patreon"
},
"install-path": "../gravure/oauth2-patreon"
},
{
"name": "mrjoops/oauth2-jira",
"version": "v0.2.4",
"version_normalized": "0.2.4.0",
"source": {
"type": "git",
"url": "https://github.com/mrjoops/oauth2-jira.git",
"reference": "9c270f7a70ea13c8d844676cddf17977991cb347"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/mrjoops/oauth2-jira/zipball/9c270f7a70ea13c8d844676cddf17977991cb347",
"reference": "9c270f7a70ea13c8d844676cddf17977991cb347",
"shasum": ""
},
"require": {
"league/oauth2-client": "^2.0"
},
"require-dev": {
"mockery/mockery": "^1.2",
"phpunit/phpunit": "^5.0",
"squizlabs/php_codesniffer": "^3.3"
},
"time": "2018-11-11T19:49:42+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Mrjoops\\OAuth2\\Client\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Alexandre Lahure",
"email": "alexandre@lahu.re"
}
],
"description": "Jira OAuth 2.0 support for the PHP League's OAuth 2.0 Client",
"keywords": [
"authorisation",
"authorization",
"client",
"jira",
"oauth",
"oauth2"
],
"support": {
"issues": "https://github.com/mrjoops/oauth2-jira/issues",
"source": "https://github.com/mrjoops/oauth2-jira/tree/develop"
},
"install-path": "../mrjoops/oauth2-jira"
},
{
"name": "omines/oauth2-gitlab",
"version": "3.4.0",
"version_normalized": "3.4.0.0",
"source": {
"type": "git",
"url": "https://github.com/omines/oauth2-gitlab.git",
"reference": "0c37361c54fae71a85350c445bda1834db5859af"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/omines/oauth2-gitlab/zipball/0c37361c54fae71a85350c445bda1834db5859af",
"reference": "0c37361c54fae71a85350c445bda1834db5859af",
"shasum": ""
},
"require": {
"league/oauth2-client": "^2.4.1",
"php": ">=7.2"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.0",
"guzzlehttp/psr7": "^1.6",
"http-interop/http-factory-guzzle": "^1.0",
"m4tthumphrey/php-gitlab-api": "^10.0|^11.0",
"mockery/mockery": "^1.0",
"php-http/guzzle7-adapter": "^0.1",
"phpunit/phpunit": "^8.0|^9.0"
},
"suggest": {
"m4tthumphrey/php-gitlab-api": "For further API usage using the acquired OAuth2 token"
},
"time": "2021-02-08T12:15:55+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Omines\\OAuth2\\Client\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Niels Keurentjes",
"email": "niels.keurentjes@omines.com",
"homepage": "https://www.omines.nl/"
}
],
"description": "GitLab OAuth 2.0 Client Provider for The PHP League OAuth2-Client",
"keywords": [
"authorisation",
"authorization",
"client",
"gitlab",
"oauth",
"oauth2"
],
"support": {
"issues": "https://github.com/omines/oauth2-gitlab/issues",
"source": "https://github.com/omines/oauth2-gitlab/tree/3.4.0"
},
"install-path": "../omines/oauth2-gitlab"
},
{
"name": "stevenmaguire/oauth2-keycloak",
"version": "3.0.0",
"version_normalized": "3.0.0.0",
"source": {
"type": "git",
"url": "https://github.com/stevenmaguire/oauth2-keycloak.git",
"reference": "645b84107b82a08cfed9c101081eb8548ea5be11"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/stevenmaguire/oauth2-keycloak/zipball/645b84107b82a08cfed9c101081eb8548ea5be11",
"reference": "645b84107b82a08cfed9c101081eb8548ea5be11",
"shasum": ""
},
"require": {
"firebase/php-jwt": "~4.0|~5.0",
"league/oauth2-client": "^2.0 <2.3.0"
},
"require-dev": {
"mockery/mockery": "~0.9",
"phpunit/phpunit": "~4.0",
"squizlabs/php_codesniffer": "~2.0"
},
"time": "2022-01-23T18:01:00+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Stevenmaguire\\OAuth2\\Client\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Steven Maguire",
"email": "stevenmaguire@gmail.com",
"homepage": "https://github.com/stevenmaguire"
}
],
"description": "Keycloak OAuth 2.0 Client Provider for The PHP League OAuth2-Client",
"keywords": [
"authorisation",
"authorization",
"client",
"keycloak",
"oauth",
"oauth2"
],
"support": {
"issues": "https://github.com/stevenmaguire/oauth2-keycloak/issues",
"source": "https://github.com/stevenmaguire/oauth2-keycloak/tree/3.0.0"
},
"install-path": "../stevenmaguire/oauth2-keycloak"
},
{
"name": "thenetworg/oauth2-azure",
"version": "v1.4.0",
"version_normalized": "1.4.0.0",
"source": {
"type": "git",
"url": "https://github.com/TheNetworg/oauth2-azure.git",
"reference": "c57dcb63a925c29e744bffa4a079a95680dd5faf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/TheNetworg/oauth2-azure/zipball/c57dcb63a925c29e744bffa4a079a95680dd5faf",
"reference": "c57dcb63a925c29e744bffa4a079a95680dd5faf",
"shasum": ""
},
"require": {
"firebase/php-jwt": "~3.0||~4.0||~5.0",
"league/oauth2-client": "~2.0",
"php": ">=5.5.0"
},
"time": "2018-10-02T08:54:26+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"TheNetworg\\OAuth2\\Client\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jan Hajek",
"email": "jan.hajek@thenetw.org",
"homepage": "https://thenetw.org"
}
],
"description": "Azure Active Directory OAuth 2.0 Client Provider for The PHP League OAuth2-Client",
"keywords": [
"SSO",
"aad",
"authorization",
"azure",
"azure active directory",
"client",
"microsoft",
"oauth",
"oauth2",
"windows azure"
],
"support": {
"issues": "https://github.com/TheNetworg/oauth2-azure/issues",
"source": "https://github.com/TheNetworg/oauth2-azure/tree/master"
},
"install-path": "../thenetworg/oauth2-azure"
},
{
"name": "vertisan/oauth2-twitch-helix",
"version": "1.1.2",
"version_normalized": "1.1.2.0",
"source": {
"type": "git",
"url": "https://github.com/vertisan/oauth2-twitch-helix.git",
"reference": "67cac44b4adf113aee026424b435975a026db0e9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/vertisan/oauth2-twitch-helix/zipball/67cac44b4adf113aee026424b435975a026db0e9",
"reference": "67cac44b4adf113aee026424b435975a026db0e9",
"shasum": ""
},
"require": {
"league/oauth2-client": "^2.2.1",
"php": ">=5.6.0"
},
"require-dev": {
"ext-json": "*",
"jakub-onderka/php-parallel-lint": "^1.0",
"mockery/mockery": "^1.2",
"phpunit/phpunit": "^5.7",
"squizlabs/php_codesniffer": "^3.4"
},
"time": "2022-02-03T20:38:26+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Vertisan\\OAuth2\\Client\\Provider\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Paweł Farys",
"email": "pmg.farys@gmail.com",
"homepage": "https://github.com/vertisan"
}
],
"description": "Twitch (new version Helix) OAuth 2.0 Client Provider for The PHP League OAuth2-Client",
"keywords": [
"client",
"helix",
"league",
"oauth",
"package",
"twitch"
],
"support": {
"issues": "https://github.com/vertisan/oauth2-twitch-helix/issues",
"source": "https://github.com/vertisan/oauth2-twitch-helix/tree/1.1.2"
},
"install-path": "../vertisan/oauth2-twitch-helix"
},
{
"name": "wohali/oauth2-discord-new",
"version": "1.1.0",
"version_normalized": "1.1.0.0",
"source": {
"type": "git",
"url": "https://github.com/wohali/oauth2-discord-new.git",
"reference": "0dcb5059cded358f55ae566de9621652cf8542c6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/wohali/oauth2-discord-new/zipball/0dcb5059cded358f55ae566de9621652cf8542c6",
"reference": "0dcb5059cded358f55ae566de9621652cf8542c6",
"shasum": ""
},
"require": {
"league/oauth2-client": "^2.0"
},
"conflict": {
"team-reflex/oauth2-discord": ">=1.0"
},
"require-dev": {
"mockery/mockery": "~1.3.0",
"php-parallel-lint/php-parallel-lint": "~0.9",
"phpunit/phpunit": "~8.0",
"squizlabs/php_codesniffer": "^2.0"
},
"time": "2020-06-12T07:27:09+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Wohali\\OAuth2\\Client\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Joan Touzet",
"email": "code@atypical.net",
"homepage": "https://github.com/wohali"
}
],
"description": "Discord OAuth 2.0 Client Provider for The PHP League OAuth2-Client",
"keywords": [
"authorisation",
"authorization",
"client",
"discord",
"oauth",
"oauth2"
],
"support": {
"issues": "https://github.com/wohali/oauth2-discord-new/issues",
"source": "https://github.com/wohali/oauth2-discord-new/tree/master"
},
"install-path": "../wohali/oauth2-discord-new"
}
],
"dev": true,
"dev-package-names": []
}

View File

@ -0,0 +1,151 @@
<?php return array(
'root' => array(
'pretty_version' => 'dev-develop',
'version' => 'dev-develop',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'reference' => '21ea886e486956d4d163ed21e51c569ac666f750',
'name' => '__root__',
'dev' => true,
),
'versions' => array(
'__root__' => array(
'pretty_version' => 'dev-develop',
'version' => 'dev-develop',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'reference' => '21ea886e486956d4d163ed21e51c569ac666f750',
'dev_requirement' => false,
),
'adam-paterson/oauth2-slack' => array(
'pretty_version' => '1.1.3',
'version' => '1.1.3.0',
'type' => 'library',
'install_path' => __DIR__ . '/../adam-paterson/oauth2-slack',
'aliases' => array(),
'reference' => 'ccc329eb3036a89d110227a4137e15d4a5661678',
'dev_requirement' => false,
),
'firebase/php-jwt' => array(
'pretty_version' => 'v5.5.1',
'version' => '5.5.1.0',
'type' => 'library',
'install_path' => __DIR__ . '/../firebase/php-jwt',
'aliases' => array(),
'reference' => '83b609028194aa042ea33b5af2d41a7427de80e6',
'dev_requirement' => false,
),
'foxworth42/oauth2-okta' => array(
'pretty_version' => 'v1.0.2',
'version' => '1.0.2.0',
'type' => 'library',
'install_path' => __DIR__ . '/../foxworth42/oauth2-okta',
'aliases' => array(),
'reference' => '0e7c2eb68f57eff8aafc4a3f0a1a1ec1c147c946',
'dev_requirement' => false,
),
'gravure/oauth2-patreon' => array(
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'type' => 'library',
'install_path' => __DIR__ . '/../gravure/oauth2-patreon',
'aliases' => array(
0 => '9999999-dev',
),
'reference' => '32c5bb7c6cdfb0cbb4e396ca8e9cbde447f41f47',
'dev_requirement' => false,
),
'guzzlehttp/guzzle' => array(
'dev_requirement' => false,
'replaced' => array(
0 => '*',
),
),
'guzzlehttp/promises' => array(
'dev_requirement' => false,
'replaced' => array(
0 => '*',
),
),
'guzzlehttp/psr7' => array(
'dev_requirement' => false,
'replaced' => array(
0 => '*',
),
),
'league/oauth2-client' => array(
'dev_requirement' => false,
'replaced' => array(
0 => '*',
),
),
'mrjoops/oauth2-jira' => array(
'pretty_version' => 'v0.2.4',
'version' => '0.2.4.0',
'type' => 'library',
'install_path' => __DIR__ . '/../mrjoops/oauth2-jira',
'aliases' => array(),
'reference' => '9c270f7a70ea13c8d844676cddf17977991cb347',
'dev_requirement' => false,
),
'omines/oauth2-gitlab' => array(
'pretty_version' => '3.4.0',
'version' => '3.4.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../omines/oauth2-gitlab',
'aliases' => array(),
'reference' => '0c37361c54fae71a85350c445bda1834db5859af',
'dev_requirement' => false,
),
'paragonie/random_compat' => array(
'dev_requirement' => false,
'replaced' => array(
0 => '*',
),
),
'psr/http-message' => array(
'dev_requirement' => false,
'replaced' => array(
0 => '*',
),
),
'stevenmaguire/oauth2-keycloak' => array(
'pretty_version' => '3.0.0',
'version' => '3.0.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../stevenmaguire/oauth2-keycloak',
'aliases' => array(),
'reference' => '645b84107b82a08cfed9c101081eb8548ea5be11',
'dev_requirement' => false,
),
'thenetworg/oauth2-azure' => array(
'pretty_version' => 'v1.4.0',
'version' => '1.4.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../thenetworg/oauth2-azure',
'aliases' => array(),
'reference' => 'c57dcb63a925c29e744bffa4a079a95680dd5faf',
'dev_requirement' => false,
),
'vertisan/oauth2-twitch-helix' => array(
'pretty_version' => '1.1.2',
'version' => '1.1.2.0',
'type' => 'library',
'install_path' => __DIR__ . '/../vertisan/oauth2-twitch-helix',
'aliases' => array(),
'reference' => '67cac44b4adf113aee026424b435975a026db0e9',
'dev_requirement' => false,
),
'wohali/oauth2-discord-new' => array(
'pretty_version' => '1.1.0',
'version' => '1.1.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../wohali/oauth2-discord-new',
'aliases' => array(),
'reference' => '0dcb5059cded358f55ae566de9621652cf8542c6',
'dev_requirement' => false,
),
),
);

View File

@ -0,0 +1,26 @@
<?php
// platform_check.php @generated by Composer
$issues = array();
if (!(PHP_VERSION_ID >= 70200)) {
$issues[] = 'Your Composer dependencies require a PHP version ">= 7.2.0". You are running ' . PHP_VERSION . '.';
}
if ($issues) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
} elseif (!headers_sent()) {
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
}
}
trigger_error(
'Composer detected issues in your platform: ' . implode(' ', $issues),
E_USER_ERROR
);
}

View File

@ -0,0 +1,30 @@
Copyright (c) 2011, Neuman Vong
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of the copyright holder nor the names of other
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,289 @@
[![Build Status](https://travis-ci.org/firebase/php-jwt.png?branch=master)](https://travis-ci.org/firebase/php-jwt)
[![Latest Stable Version](https://poser.pugx.org/firebase/php-jwt/v/stable)](https://packagist.org/packages/firebase/php-jwt)
[![Total Downloads](https://poser.pugx.org/firebase/php-jwt/downloads)](https://packagist.org/packages/firebase/php-jwt)
[![License](https://poser.pugx.org/firebase/php-jwt/license)](https://packagist.org/packages/firebase/php-jwt)
PHP-JWT
=======
A simple library to encode and decode JSON Web Tokens (JWT) in PHP, conforming to [RFC 7519](https://tools.ietf.org/html/rfc7519).
Installation
------------
Use composer to manage your dependencies and download PHP-JWT:
```bash
composer require firebase/php-jwt
```
Optionally, install the `paragonie/sodium_compat` package from composer if your
php is < 7.2 or does not have libsodium installed:
```bash
composer require paragonie/sodium_compat
```
Example
-------
```php
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
$key = "example_key";
$payload = array(
"iss" => "http://example.org",
"aud" => "http://example.com",
"iat" => 1356999524,
"nbf" => 1357000000
);
/**
* IMPORTANT:
* You must specify supported algorithms for your application. See
* https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40
* for a list of spec-compliant algorithms.
*/
$jwt = JWT::encode($payload, $key, 'HS256');
$decoded = JWT::decode($jwt, new Key($key, 'HS256'));
print_r($decoded);
/*
NOTE: This will now be an object instead of an associative array. To get
an associative array, you will need to cast it as such:
*/
$decoded_array = (array) $decoded;
/**
* You can add a leeway to account for when there is a clock skew times between
* the signing and verifying servers. It is recommended that this leeway should
* not be bigger than a few minutes.
*
* Source: http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html#nbfDef
*/
JWT::$leeway = 60; // $leeway in seconds
$decoded = JWT::decode($jwt, new Key($key, 'HS256'));
```
Example with RS256 (openssl)
----------------------------
```php
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
$privateKey = <<<EOD
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQC8kGa1pSjbSYZVebtTRBLxBz5H4i2p/llLCrEeQhta5kaQu/Rn
vuER4W8oDH3+3iuIYW4VQAzyqFpwuzjkDI+17t5t0tyazyZ8JXw+KgXTxldMPEL9
5+qVhgXvwtihXC1c5oGbRlEDvDF6Sa53rcFVsYJ4ehde/zUxo6UvS7UrBQIDAQAB
AoGAb/MXV46XxCFRxNuB8LyAtmLDgi/xRnTAlMHjSACddwkyKem8//8eZtw9fzxz
bWZ/1/doQOuHBGYZU8aDzzj59FZ78dyzNFoF91hbvZKkg+6wGyd/LrGVEB+Xre0J
Nil0GReM2AHDNZUYRv+HYJPIOrB0CRczLQsgFJ8K6aAD6F0CQQDzbpjYdx10qgK1
cP59UHiHjPZYC0loEsk7s+hUmT3QHerAQJMZWC11Qrn2N+ybwwNblDKv+s5qgMQ5
5tNoQ9IfAkEAxkyffU6ythpg/H0Ixe1I2rd0GbF05biIzO/i77Det3n4YsJVlDck
ZkcvY3SK2iRIL4c9yY6hlIhs+K9wXTtGWwJBAO9Dskl48mO7woPR9uD22jDpNSwe
k90OMepTjzSvlhjbfuPN1IdhqvSJTDychRwn1kIJ7LQZgQ8fVz9OCFZ/6qMCQGOb
qaGwHmUK6xzpUbbacnYrIM6nLSkXgOAwv7XXCojvY614ILTK3iXiLBOxPu5Eu13k
eUz9sHyD6vkgZzjtxXECQAkp4Xerf5TGfQXGXhxIX52yH+N2LtujCdkQZjXAsGdm
B2zNzvrlgRmgBrklMTrMYgm1NPcW+bRLGcwgW2PTvNM=
-----END RSA PRIVATE KEY-----
EOD;
$publicKey = <<<EOD
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8kGa1pSjbSYZVebtTRBLxBz5H
4i2p/llLCrEeQhta5kaQu/RnvuER4W8oDH3+3iuIYW4VQAzyqFpwuzjkDI+17t5t
0tyazyZ8JXw+KgXTxldMPEL95+qVhgXvwtihXC1c5oGbRlEDvDF6Sa53rcFVsYJ4
ehde/zUxo6UvS7UrBQIDAQAB
-----END PUBLIC KEY-----
EOD;
$payload = array(
"iss" => "example.org",
"aud" => "example.com",
"iat" => 1356999524,
"nbf" => 1357000000
);
$jwt = JWT::encode($payload, $privateKey, 'RS256');
echo "Encode:\n" . print_r($jwt, true) . "\n";
$decoded = JWT::decode($jwt, new Key($publicKey, 'RS256'));
/*
NOTE: This will now be an object instead of an associative array. To get
an associative array, you will need to cast it as such:
*/
$decoded_array = (array) $decoded;
echo "Decode:\n" . print_r($decoded_array, true) . "\n";
```
Example with a passphrase
-------------------------
```php
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
// Your passphrase
$passphrase = '[YOUR_PASSPHRASE]';
// Your private key file with passphrase
// Can be generated with "ssh-keygen -t rsa -m pem"
$privateKeyFile = '/path/to/key-with-passphrase.pem';
// Create a private key of type "resource"
$privateKey = openssl_pkey_get_private(
file_get_contents($privateKeyFile),
$passphrase
);
$payload = array(
"iss" => "example.org",
"aud" => "example.com",
"iat" => 1356999524,
"nbf" => 1357000000
);
$jwt = JWT::encode($payload, $privateKey, 'RS256');
echo "Encode:\n" . print_r($jwt, true) . "\n";
// Get public key from the private key, or pull from from a file.
$publicKey = openssl_pkey_get_details($privateKey)['key'];
$decoded = JWT::decode($jwt, new Key($publicKey, 'RS256'));
echo "Decode:\n" . print_r((array) $decoded, true) . "\n";
```
Example with EdDSA (libsodium and Ed25519 signature)
----------------------------
```php
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
// Public and private keys are expected to be Base64 encoded. The last
// non-empty line is used so that keys can be generated with
// sodium_crypto_sign_keypair(). The secret keys generated by other tools may
// need to be adjusted to match the input expected by libsodium.
$keyPair = sodium_crypto_sign_keypair();
$privateKey = base64_encode(sodium_crypto_sign_secretkey($keyPair));
$publicKey = base64_encode(sodium_crypto_sign_publickey($keyPair));
$payload = array(
"iss" => "example.org",
"aud" => "example.com",
"iat" => 1356999524,
"nbf" => 1357000000
);
$jwt = JWT::encode($payload, $privateKey, 'EdDSA');
echo "Encode:\n" . print_r($jwt, true) . "\n";
$decoded = JWT::decode($jwt, new Key($publicKey, 'EdDSA'));
echo "Decode:\n" . print_r((array) $decoded, true) . "\n";
````
Using JWKs
----------
```php
use Firebase\JWT\JWK;
use Firebase\JWT\JWT;
// Set of keys. The "keys" key is required. For example, the JSON response to
// this endpoint: https://www.gstatic.com/iap/verify/public_key-jwk
$jwks = ['keys' => []];
// JWK::parseKeySet($jwks) returns an associative array of **kid** to private
// key. Pass this as the second parameter to JWT::decode.
// NOTE: The deprecated $supportedAlgorithm must be supplied when parsing from JWK.
JWT::decode($payload, JWK::parseKeySet($jwks), $supportedAlgorithm);
```
Changelog
---------
#### 5.0.0 / 2017-06-26
- Support RS384 and RS512.
See [#117](https://github.com/firebase/php-jwt/pull/117). Thanks [@joostfaassen](https://github.com/joostfaassen)!
- Add an example for RS256 openssl.
See [#125](https://github.com/firebase/php-jwt/pull/125). Thanks [@akeeman](https://github.com/akeeman)!
- Detect invalid Base64 encoding in signature.
See [#162](https://github.com/firebase/php-jwt/pull/162). Thanks [@psignoret](https://github.com/psignoret)!
- Update `JWT::verify` to handle OpenSSL errors.
See [#159](https://github.com/firebase/php-jwt/pull/159). Thanks [@bshaffer](https://github.com/bshaffer)!
- Add `array` type hinting to `decode` method
See [#101](https://github.com/firebase/php-jwt/pull/101). Thanks [@hywak](https://github.com/hywak)!
- Add all JSON error types.
See [#110](https://github.com/firebase/php-jwt/pull/110). Thanks [@gbalduzzi](https://github.com/gbalduzzi)!
- Bugfix 'kid' not in given key list.
See [#129](https://github.com/firebase/php-jwt/pull/129). Thanks [@stampycode](https://github.com/stampycode)!
- Miscellaneous cleanup, documentation and test fixes.
See [#107](https://github.com/firebase/php-jwt/pull/107), [#115](https://github.com/firebase/php-jwt/pull/115),
[#160](https://github.com/firebase/php-jwt/pull/160), [#161](https://github.com/firebase/php-jwt/pull/161), and
[#165](https://github.com/firebase/php-jwt/pull/165). Thanks [@akeeman](https://github.com/akeeman),
[@chinedufn](https://github.com/chinedufn), and [@bshaffer](https://github.com/bshaffer)!
#### 4.0.0 / 2016-07-17
- Add support for late static binding. See [#88](https://github.com/firebase/php-jwt/pull/88) for details. Thanks to [@chappy84](https://github.com/chappy84)!
- Use static `$timestamp` instead of `time()` to improve unit testing. See [#93](https://github.com/firebase/php-jwt/pull/93) for details. Thanks to [@josephmcdermott](https://github.com/josephmcdermott)!
- Fixes to exceptions classes. See [#81](https://github.com/firebase/php-jwt/pull/81) for details. Thanks to [@Maks3w](https://github.com/Maks3w)!
- Fixes to PHPDoc. See [#76](https://github.com/firebase/php-jwt/pull/76) for details. Thanks to [@akeeman](https://github.com/akeeman)!
#### 3.0.0 / 2015-07-22
- Minimum PHP version updated from `5.2.0` to `5.3.0`.
- Add `\Firebase\JWT` namespace. See
[#59](https://github.com/firebase/php-jwt/pull/59) for details. Thanks to
[@Dashron](https://github.com/Dashron)!
- Require a non-empty key to decode and verify a JWT. See
[#60](https://github.com/firebase/php-jwt/pull/60) for details. Thanks to
[@sjones608](https://github.com/sjones608)!
- Cleaner documentation blocks in the code. See
[#62](https://github.com/firebase/php-jwt/pull/62) for details. Thanks to
[@johanderuijter](https://github.com/johanderuijter)!
#### 2.2.0 / 2015-06-22
- Add support for adding custom, optional JWT headers to `JWT::encode()`. See
[#53](https://github.com/firebase/php-jwt/pull/53/files) for details. Thanks to
[@mcocaro](https://github.com/mcocaro)!
#### 2.1.0 / 2015-05-20
- Add support for adding a leeway to `JWT:decode()` that accounts for clock skew
between signing and verifying entities. Thanks to [@lcabral](https://github.com/lcabral)!
- Add support for passing an object implementing the `ArrayAccess` interface for
`$keys` argument in `JWT::decode()`. Thanks to [@aztech-dev](https://github.com/aztech-dev)!
#### 2.0.0 / 2015-04-01
- **Note**: It is strongly recommended that you update to > v2.0.0 to address
known security vulnerabilities in prior versions when both symmetric and
asymmetric keys are used together.
- Update signature for `JWT::decode(...)` to require an array of supported
algorithms to use when verifying token signatures.
Tests
-----
Run the tests using phpunit:
```bash
$ pear install PHPUnit
$ phpunit --configuration phpunit.xml.dist
PHPUnit 3.7.10 by Sebastian Bergmann.
.....
Time: 0 seconds, Memory: 2.50Mb
OK (5 tests, 5 assertions)
```
New Lines in private keys
-----
If your private key contains `\n` characters, be sure to wrap it in double quotes `""`
and not single quotes `''` in order to properly interpret the escaped characters.
License
-------
[3-Clause BSD](http://opensource.org/licenses/BSD-3-Clause).

View File

@ -0,0 +1,36 @@
{
"name": "firebase/php-jwt",
"description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.",
"homepage": "https://github.com/firebase/php-jwt",
"keywords": [
"php",
"jwt"
],
"authors": [
{
"name": "Neuman Vong",
"email": "neuman+pear@twilio.com",
"role": "Developer"
},
{
"name": "Anant Narayanan",
"email": "anant@php.net",
"role": "Developer"
}
],
"license": "BSD-3-Clause",
"require": {
"php": ">=5.3.0"
},
"suggest": {
"paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present"
},
"autoload": {
"psr-4": {
"Firebase\\JWT\\": "src"
}
},
"require-dev": {
"phpunit/phpunit": ">=4.8 <=9"
}
}

View File

@ -0,0 +1,7 @@
<?php
namespace Firebase\JWT;
class BeforeValidException extends \UnexpectedValueException
{
}

View File

@ -0,0 +1,7 @@
<?php
namespace Firebase\JWT;
class ExpiredException extends \UnexpectedValueException
{
}

View File

@ -0,0 +1,172 @@
<?php
namespace Firebase\JWT;
use DomainException;
use InvalidArgumentException;
use UnexpectedValueException;
/**
* JSON Web Key implementation, based on this spec:
* https://tools.ietf.org/html/draft-ietf-jose-json-web-key-41
*
* PHP version 5
*
* @category Authentication
* @package Authentication_JWT
* @author Bui Sy Nguyen <nguyenbs@gmail.com>
* @license http://opensource.org/licenses/BSD-3-Clause 3-clause BSD
* @link https://github.com/firebase/php-jwt
*/
class JWK
{
/**
* Parse a set of JWK keys
*
* @param array $jwks The JSON Web Key Set as an associative array
*
* @return array An associative array that represents the set of keys
*
* @throws InvalidArgumentException Provided JWK Set is empty
* @throws UnexpectedValueException Provided JWK Set was invalid
* @throws DomainException OpenSSL failure
*
* @uses parseKey
*/
public static function parseKeySet(array $jwks)
{
$keys = array();
if (!isset($jwks['keys'])) {
throw new UnexpectedValueException('"keys" member must exist in the JWK Set');
}
if (empty($jwks['keys'])) {
throw new InvalidArgumentException('JWK Set did not contain any keys');
}
foreach ($jwks['keys'] as $k => $v) {
$kid = isset($v['kid']) ? $v['kid'] : $k;
if ($key = self::parseKey($v)) {
$keys[$kid] = $key;
}
}
if (0 === \count($keys)) {
throw new UnexpectedValueException('No supported algorithms found in JWK Set');
}
return $keys;
}
/**
* Parse a JWK key
*
* @param array $jwk An individual JWK
*
* @return resource|array An associative array that represents the key
*
* @throws InvalidArgumentException Provided JWK is empty
* @throws UnexpectedValueException Provided JWK was invalid
* @throws DomainException OpenSSL failure
*
* @uses createPemFromModulusAndExponent
*/
public static function parseKey(array $jwk)
{
if (empty($jwk)) {
throw new InvalidArgumentException('JWK must not be empty');
}
if (!isset($jwk['kty'])) {
throw new UnexpectedValueException('JWK must contain a "kty" parameter');
}
switch ($jwk['kty']) {
case 'RSA':
if (!empty($jwk['d'])) {
throw new UnexpectedValueException('RSA private keys are not supported');
}
if (!isset($jwk['n']) || !isset($jwk['e'])) {
throw new UnexpectedValueException('RSA keys must contain values for both "n" and "e"');
}
$pem = self::createPemFromModulusAndExponent($jwk['n'], $jwk['e']);
$publicKey = \openssl_pkey_get_public($pem);
if (false === $publicKey) {
throw new DomainException(
'OpenSSL error: ' . \openssl_error_string()
);
}
return $publicKey;
default:
// Currently only RSA is supported
break;
}
}
/**
* Create a public key represented in PEM format from RSA modulus and exponent information
*
* @param string $n The RSA modulus encoded in Base64
* @param string $e The RSA exponent encoded in Base64
*
* @return string The RSA public key represented in PEM format
*
* @uses encodeLength
*/
private static function createPemFromModulusAndExponent($n, $e)
{
$modulus = JWT::urlsafeB64Decode($n);
$publicExponent = JWT::urlsafeB64Decode($e);
$components = array(
'modulus' => \pack('Ca*a*', 2, self::encodeLength(\strlen($modulus)), $modulus),
'publicExponent' => \pack('Ca*a*', 2, self::encodeLength(\strlen($publicExponent)), $publicExponent)
);
$rsaPublicKey = \pack(
'Ca*a*a*',
48,
self::encodeLength(\strlen($components['modulus']) + \strlen($components['publicExponent'])),
$components['modulus'],
$components['publicExponent']
);
// sequence(oid(1.2.840.113549.1.1.1), null)) = rsaEncryption.
$rsaOID = \pack('H*', '300d06092a864886f70d0101010500'); // hex version of MA0GCSqGSIb3DQEBAQUA
$rsaPublicKey = \chr(0) . $rsaPublicKey;
$rsaPublicKey = \chr(3) . self::encodeLength(\strlen($rsaPublicKey)) . $rsaPublicKey;
$rsaPublicKey = \pack(
'Ca*a*',
48,
self::encodeLength(\strlen($rsaOID . $rsaPublicKey)),
$rsaOID . $rsaPublicKey
);
$rsaPublicKey = "-----BEGIN PUBLIC KEY-----\r\n" .
\chunk_split(\base64_encode($rsaPublicKey), 64) .
'-----END PUBLIC KEY-----';
return $rsaPublicKey;
}
/**
* DER-encode the length
*
* DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4. See
* {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3} for more information.
*
* @param int $length
* @return string
*/
private static function encodeLength($length)
{
if ($length <= 0x7F) {
return \chr($length);
}
$temp = \ltrim(\pack('N', $length), \chr(0));
return \pack('Ca*', 0x80 | \strlen($temp), $temp);
}
}

View File

@ -0,0 +1,611 @@
<?php
namespace Firebase\JWT;
use ArrayAccess;
use DomainException;
use Exception;
use InvalidArgumentException;
use OpenSSLAsymmetricKey;
use UnexpectedValueException;
use DateTime;
/**
* JSON Web Token implementation, based on this spec:
* https://tools.ietf.org/html/rfc7519
*
* PHP version 5
*
* @category Authentication
* @package Authentication_JWT
* @author Neuman Vong <neuman@twilio.com>
* @author Anant Narayanan <anant@php.net>
* @license http://opensource.org/licenses/BSD-3-Clause 3-clause BSD
* @link https://github.com/firebase/php-jwt
*/
class JWT
{
const ASN1_INTEGER = 0x02;
const ASN1_SEQUENCE = 0x10;
const ASN1_BIT_STRING = 0x03;
/**
* When checking nbf, iat or expiration times,
* we want to provide some extra leeway time to
* account for clock skew.
*/
public static $leeway = 0;
/**
* Allow the current timestamp to be specified.
* Useful for fixing a value within unit testing.
*
* Will default to PHP time() value if null.
*/
public static $timestamp = null;
public static $supported_algs = array(
'ES384' => array('openssl', 'SHA384'),
'ES256' => array('openssl', 'SHA256'),
'HS256' => array('hash_hmac', 'SHA256'),
'HS384' => array('hash_hmac', 'SHA384'),
'HS512' => array('hash_hmac', 'SHA512'),
'RS256' => array('openssl', 'SHA256'),
'RS384' => array('openssl', 'SHA384'),
'RS512' => array('openssl', 'SHA512'),
'EdDSA' => array('sodium_crypto', 'EdDSA'),
);
/**
* Decodes a JWT string into a PHP object.
*
* @param string $jwt The JWT
* @param Key|array<Key>|mixed $keyOrKeyArray The Key or array of Key objects.
* If the algorithm used is asymmetric, this is the public key
* Each Key object contains an algorithm and matching key.
* Supported algorithms are 'ES384','ES256', 'HS256', 'HS384',
* 'HS512', 'RS256', 'RS384', and 'RS512'
* @param array $allowed_algs [DEPRECATED] List of supported verification algorithms. Only
* should be used for backwards compatibility.
*
* @return object The JWT's payload as a PHP object
*
* @throws InvalidArgumentException Provided JWT was empty
* @throws UnexpectedValueException Provided JWT was invalid
* @throws SignatureInvalidException Provided JWT was invalid because the signature verification failed
* @throws BeforeValidException Provided JWT is trying to be used before it's eligible as defined by 'nbf'
* @throws BeforeValidException Provided JWT is trying to be used before it's been created as defined by 'iat'
* @throws ExpiredException Provided JWT has since expired, as defined by the 'exp' claim
*
* @uses jsonDecode
* @uses urlsafeB64Decode
*/
public static function decode($jwt, $keyOrKeyArray, array $allowed_algs = array())
{
$timestamp = \is_null(static::$timestamp) ? \time() : static::$timestamp;
if (empty($keyOrKeyArray)) {
throw new InvalidArgumentException('Key may not be empty');
}
$tks = \explode('.', $jwt);
if (\count($tks) != 3) {
throw new UnexpectedValueException('Wrong number of segments');
}
list($headb64, $bodyb64, $cryptob64) = $tks;
if (null === ($header = static::jsonDecode(static::urlsafeB64Decode($headb64)))) {
throw new UnexpectedValueException('Invalid header encoding');
}
if (null === $payload = static::jsonDecode(static::urlsafeB64Decode($bodyb64))) {
throw new UnexpectedValueException('Invalid claims encoding');
}
if (false === ($sig = static::urlsafeB64Decode($cryptob64))) {
throw new UnexpectedValueException('Invalid signature encoding');
}
if (empty($header->alg)) {
throw new UnexpectedValueException('Empty algorithm');
}
if (empty(static::$supported_algs[$header->alg])) {
throw new UnexpectedValueException('Algorithm not supported');
}
list($keyMaterial, $algorithm) = self::getKeyMaterialAndAlgorithm(
$keyOrKeyArray,
empty($header->kid) ? null : $header->kid
);
if (empty($algorithm)) {
// Use deprecated "allowed_algs" to determine if the algorithm is supported.
// This opens up the possibility of an attack in some implementations.
// @see https://github.com/firebase/php-jwt/issues/351
if (!\in_array($header->alg, $allowed_algs)) {
throw new UnexpectedValueException('Algorithm not allowed');
}
} else {
// Check the algorithm
if (!self::constantTimeEquals($algorithm, $header->alg)) {
// See issue #351
throw new UnexpectedValueException('Incorrect key for this algorithm');
}
}
if ($header->alg === 'ES256' || $header->alg === 'ES384') {
// OpenSSL expects an ASN.1 DER sequence for ES256/ES384 signatures
$sig = self::signatureToDER($sig);
}
if (!static::verify("$headb64.$bodyb64", $sig, $keyMaterial, $header->alg)) {
throw new SignatureInvalidException('Signature verification failed');
}
// Check the nbf if it is defined. This is the time that the
// token can actually be used. If it's not yet that time, abort.
if (isset($payload->nbf) && $payload->nbf > ($timestamp + static::$leeway)) {
throw new BeforeValidException(
'Cannot handle token prior to ' . \date(DateTime::ISO8601, $payload->nbf)
);
}
// Check that this token has been created before 'now'. This prevents
// using tokens that have been created for later use (and haven't
// correctly used the nbf claim).
if (isset($payload->iat) && $payload->iat > ($timestamp + static::$leeway)) {
throw new BeforeValidException(
'Cannot handle token prior to ' . \date(DateTime::ISO8601, $payload->iat)
);
}
// Check if this token has expired.
if (isset($payload->exp) && ($timestamp - static::$leeway) >= $payload->exp) {
throw new ExpiredException('Expired token');
}
return $payload;
}
/**
* Converts and signs a PHP object or array into a JWT string.
*
* @param object|array $payload PHP object or array
* @param string|resource $key The secret key.
* If the algorithm used is asymmetric, this is the private key
* @param string $alg The signing algorithm.
* Supported algorithms are 'ES384','ES256', 'HS256', 'HS384',
* 'HS512', 'RS256', 'RS384', and 'RS512'
* @param mixed $keyId
* @param array $head An array with header elements to attach
*
* @return string A signed JWT
*
* @uses jsonEncode
* @uses urlsafeB64Encode
*/
public static function encode($payload, $key, $alg = 'HS256', $keyId = null, $head = null)
{
$header = array('typ' => 'JWT', 'alg' => $alg);
if ($keyId !== null) {
$header['kid'] = $keyId;
}
if (isset($head) && \is_array($head)) {
$header = \array_merge($head, $header);
}
$segments = array();
$segments[] = static::urlsafeB64Encode(static::jsonEncode($header));
$segments[] = static::urlsafeB64Encode(static::jsonEncode($payload));
$signing_input = \implode('.', $segments);
$signature = static::sign($signing_input, $key, $alg);
$segments[] = static::urlsafeB64Encode($signature);
return \implode('.', $segments);
}
/**
* Sign a string with a given key and algorithm.
*
* @param string $msg The message to sign
* @param string|resource $key The secret key
* @param string $alg The signing algorithm.
* Supported algorithms are 'ES384','ES256', 'HS256', 'HS384',
* 'HS512', 'RS256', 'RS384', and 'RS512'
*
* @return string An encrypted message
*
* @throws DomainException Unsupported algorithm or bad key was specified
*/
public static function sign($msg, $key, $alg = 'HS256')
{
if (empty(static::$supported_algs[$alg])) {
throw new DomainException('Algorithm not supported');
}
list($function, $algorithm) = static::$supported_algs[$alg];
switch ($function) {
case 'hash_hmac':
return \hash_hmac($algorithm, $msg, $key, true);
case 'openssl':
$signature = '';
$success = \openssl_sign($msg, $signature, $key, $algorithm);
if (!$success) {
throw new DomainException("OpenSSL unable to sign data");
}
if ($alg === 'ES256') {
$signature = self::signatureFromDER($signature, 256);
} elseif ($alg === 'ES384') {
$signature = self::signatureFromDER($signature, 384);
}
return $signature;
case 'sodium_crypto':
if (!function_exists('sodium_crypto_sign_detached')) {
throw new DomainException('libsodium is not available');
}
try {
// The last non-empty line is used as the key.
$lines = array_filter(explode("\n", $key));
$key = base64_decode(end($lines));
return sodium_crypto_sign_detached($msg, $key);
} catch (Exception $e) {
throw new DomainException($e->getMessage(), 0, $e);
}
}
}
/**
* Verify a signature with the message, key and method. Not all methods
* are symmetric, so we must have a separate verify and sign method.
*
* @param string $msg The original message (header and body)
* @param string $signature The original signature
* @param string|resource $key For HS*, a string key works. for RS*, must be a resource of an openssl public key
* @param string $alg The algorithm
*
* @return bool
*
* @throws DomainException Invalid Algorithm, bad key, or OpenSSL failure
*/
private static function verify($msg, $signature, $key, $alg)
{
if (empty(static::$supported_algs[$alg])) {
throw new DomainException('Algorithm not supported');
}
list($function, $algorithm) = static::$supported_algs[$alg];
switch ($function) {
case 'openssl':
$success = \openssl_verify($msg, $signature, $key, $algorithm);
if ($success === 1) {
return true;
} elseif ($success === 0) {
return false;
}
// returns 1 on success, 0 on failure, -1 on error.
throw new DomainException(
'OpenSSL error: ' . \openssl_error_string()
);
case 'sodium_crypto':
if (!function_exists('sodium_crypto_sign_verify_detached')) {
throw new DomainException('libsodium is not available');
}
try {
// The last non-empty line is used as the key.
$lines = array_filter(explode("\n", $key));
$key = base64_decode(end($lines));
return sodium_crypto_sign_verify_detached($signature, $msg, $key);
} catch (Exception $e) {
throw new DomainException($e->getMessage(), 0, $e);
}
case 'hash_hmac':
default:
$hash = \hash_hmac($algorithm, $msg, $key, true);
return self::constantTimeEquals($signature, $hash);
}
}
/**
* Decode a JSON string into a PHP object.
*
* @param string $input JSON string
*
* @return object Object representation of JSON string
*
* @throws DomainException Provided string was invalid JSON
*/
public static function jsonDecode($input)
{
if (\version_compare(PHP_VERSION, '5.4.0', '>=') && !(\defined('JSON_C_VERSION') && PHP_INT_SIZE > 4)) {
/** In PHP >=5.4.0, json_decode() accepts an options parameter, that allows you
* to specify that large ints (like Steam Transaction IDs) should be treated as
* strings, rather than the PHP default behaviour of converting them to floats.
*/
$obj = \json_decode($input, false, 512, JSON_BIGINT_AS_STRING);
} else {
/** Not all servers will support that, however, so for older versions we must
* manually detect large ints in the JSON string and quote them (thus converting
*them to strings) before decoding, hence the preg_replace() call.
*/
$max_int_length = \strlen((string) PHP_INT_MAX) - 1;
$json_without_bigints = \preg_replace('/:\s*(-?\d{'.$max_int_length.',})/', ': "$1"', $input);
$obj = \json_decode($json_without_bigints);
}
if ($errno = \json_last_error()) {
static::handleJsonError($errno);
} elseif ($obj === null && $input !== 'null') {
throw new DomainException('Null result with non-null input');
}
return $obj;
}
/**
* Encode a PHP object into a JSON string.
*
* @param object|array $input A PHP object or array
*
* @return string JSON representation of the PHP object or array
*
* @throws DomainException Provided object could not be encoded to valid JSON
*/
public static function jsonEncode($input)
{
$json = \json_encode($input);
if ($errno = \json_last_error()) {
static::handleJsonError($errno);
} elseif ($json === 'null' && $input !== null) {
throw new DomainException('Null result with non-null input');
}
return $json;
}
/**
* Decode a string with URL-safe Base64.
*
* @param string $input A Base64 encoded string
*
* @return string A decoded string
*/
public static function urlsafeB64Decode($input)
{
$remainder = \strlen($input) % 4;
if ($remainder) {
$padlen = 4 - $remainder;
$input .= \str_repeat('=', $padlen);
}
return \base64_decode(\strtr($input, '-_', '+/'));
}
/**
* Encode a string with URL-safe Base64.
*
* @param string $input The string you want encoded
*
* @return string The base64 encode of what you passed in
*/
public static function urlsafeB64Encode($input)
{
return \str_replace('=', '', \strtr(\base64_encode($input), '+/', '-_'));
}
/**
* Determine if an algorithm has been provided for each Key
*
* @param Key|array<Key>|mixed $keyOrKeyArray
* @param string|null $kid
*
* @throws UnexpectedValueException
*
* @return array containing the keyMaterial and algorithm
*/
private static function getKeyMaterialAndAlgorithm($keyOrKeyArray, $kid = null)
{
if (
is_string($keyOrKeyArray)
|| is_resource($keyOrKeyArray)
|| $keyOrKeyArray instanceof OpenSSLAsymmetricKey
) {
return array($keyOrKeyArray, null);
}
if ($keyOrKeyArray instanceof Key) {
return array($keyOrKeyArray->getKeyMaterial(), $keyOrKeyArray->getAlgorithm());
}
if (is_array($keyOrKeyArray) || $keyOrKeyArray instanceof ArrayAccess) {
if (!isset($kid)) {
throw new UnexpectedValueException('"kid" empty, unable to lookup correct key');
}
if (!isset($keyOrKeyArray[$kid])) {
throw new UnexpectedValueException('"kid" invalid, unable to lookup correct key');
}
$key = $keyOrKeyArray[$kid];
if ($key instanceof Key) {
return array($key->getKeyMaterial(), $key->getAlgorithm());
}
return array($key, null);
}
throw new UnexpectedValueException(
'$keyOrKeyArray must be a string|resource key, an array of string|resource keys, '
. 'an instance of Firebase\JWT\Key key or an array of Firebase\JWT\Key keys'
);
}
/**
* @param string $left
* @param string $right
* @return bool
*/
public static function constantTimeEquals($left, $right)
{
if (\function_exists('hash_equals')) {
return \hash_equals($left, $right);
}
$len = \min(static::safeStrlen($left), static::safeStrlen($right));
$status = 0;
for ($i = 0; $i < $len; $i++) {
$status |= (\ord($left[$i]) ^ \ord($right[$i]));
}
$status |= (static::safeStrlen($left) ^ static::safeStrlen($right));
return ($status === 0);
}
/**
* Helper method to create a JSON error.
*
* @param int $errno An error number from json_last_error()
*
* @return void
*/
private static function handleJsonError($errno)
{
$messages = array(
JSON_ERROR_DEPTH => 'Maximum stack depth exceeded',
JSON_ERROR_STATE_MISMATCH => 'Invalid or malformed JSON',
JSON_ERROR_CTRL_CHAR => 'Unexpected control character found',
JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON',
JSON_ERROR_UTF8 => 'Malformed UTF-8 characters' //PHP >= 5.3.3
);
throw new DomainException(
isset($messages[$errno])
? $messages[$errno]
: 'Unknown JSON error: ' . $errno
);
}
/**
* Get the number of bytes in cryptographic strings.
*
* @param string $str
*
* @return int
*/
private static function safeStrlen($str)
{
if (\function_exists('mb_strlen')) {
return \mb_strlen($str, '8bit');
}
return \strlen($str);
}
/**
* Convert an ECDSA signature to an ASN.1 DER sequence
*
* @param string $sig The ECDSA signature to convert
* @return string The encoded DER object
*/
private static function signatureToDER($sig)
{
// Separate the signature into r-value and s-value
list($r, $s) = \str_split($sig, (int) (\strlen($sig) / 2));
// Trim leading zeros
$r = \ltrim($r, "\x00");
$s = \ltrim($s, "\x00");
// Convert r-value and s-value from unsigned big-endian integers to
// signed two's complement
if (\ord($r[0]) > 0x7f) {
$r = "\x00" . $r;
}
if (\ord($s[0]) > 0x7f) {
$s = "\x00" . $s;
}
return self::encodeDER(
self::ASN1_SEQUENCE,
self::encodeDER(self::ASN1_INTEGER, $r) .
self::encodeDER(self::ASN1_INTEGER, $s)
);
}
/**
* Encodes a value into a DER object.
*
* @param int $type DER tag
* @param string $value the value to encode
* @return string the encoded object
*/
private static function encodeDER($type, $value)
{
$tag_header = 0;
if ($type === self::ASN1_SEQUENCE) {
$tag_header |= 0x20;
}
// Type
$der = \chr($tag_header | $type);
// Length
$der .= \chr(\strlen($value));
return $der . $value;
}
/**
* Encodes signature from a DER object.
*
* @param string $der binary signature in DER format
* @param int $keySize the number of bits in the key
* @return string the signature
*/
private static function signatureFromDER($der, $keySize)
{
// OpenSSL returns the ECDSA signatures as a binary ASN.1 DER SEQUENCE
list($offset, $_) = self::readDER($der);
list($offset, $r) = self::readDER($der, $offset);
list($offset, $s) = self::readDER($der, $offset);
// Convert r-value and s-value from signed two's compliment to unsigned
// big-endian integers
$r = \ltrim($r, "\x00");
$s = \ltrim($s, "\x00");
// Pad out r and s so that they are $keySize bits long
$r = \str_pad($r, $keySize / 8, "\x00", STR_PAD_LEFT);
$s = \str_pad($s, $keySize / 8, "\x00", STR_PAD_LEFT);
return $r . $s;
}
/**
* Reads binary DER-encoded data and decodes into a single object
*
* @param string $der the binary data in DER format
* @param int $offset the offset of the data stream containing the object
* to decode
* @return array [$offset, $data] the new offset and the decoded object
*/
private static function readDER($der, $offset = 0)
{
$pos = $offset;
$size = \strlen($der);
$constructed = (\ord($der[$pos]) >> 5) & 0x01;
$type = \ord($der[$pos++]) & 0x1f;
// Length
$len = \ord($der[$pos++]);
if ($len & 0x80) {
$n = $len & 0x1f;
$len = 0;
while ($n-- && $pos < $size) {
$len = ($len << 8) | \ord($der[$pos++]);
}
}
// Value
if ($type == self::ASN1_BIT_STRING) {
$pos++; // Skip the first contents octet (padding indicator)
$data = \substr($der, $pos, $len - 1);
$pos += $len - 1;
} elseif (!$constructed) {
$data = \substr($der, $pos, $len);
$pos += $len;
} else {
$data = null;
}
return array($pos, $data);
}
}

View File

@ -0,0 +1,59 @@
<?php
namespace Firebase\JWT;
use InvalidArgumentException;
use OpenSSLAsymmetricKey;
class Key
{
/** @var string $algorithm */
private $algorithm;
/** @var string|resource|OpenSSLAsymmetricKey $keyMaterial */
private $keyMaterial;
/**
* @param string|resource|OpenSSLAsymmetricKey $keyMaterial
* @param string $algorithm
*/
public function __construct($keyMaterial, $algorithm)
{
if (
!is_string($keyMaterial)
&& !is_resource($keyMaterial)
&& !$keyMaterial instanceof OpenSSLAsymmetricKey
) {
throw new InvalidArgumentException('Type error: $keyMaterial must be a string, resource, or OpenSSLAsymmetricKey');
}
if (empty($keyMaterial)) {
throw new InvalidArgumentException('Type error: $keyMaterial must not be empty');
}
if (!is_string($algorithm)|| empty($keyMaterial)) {
throw new InvalidArgumentException('Type error: $algorithm must be a string');
}
$this->keyMaterial = $keyMaterial;
$this->algorithm = $algorithm;
}
/**
* Return the algorithm valid for this key
*
* @return string
*/
public function getAlgorithm()
{
return $this->algorithm;
}
/**
* @return string|resource|OpenSSLAsymmetricKey
*/
public function getKeyMaterial()
{
return $this->keyMaterial;
}
}

View File

@ -0,0 +1,7 @@
<?php
namespace Firebase\JWT;
class SignatureInvalidException extends \UnexpectedValueException
{
}

View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015 Woody Gilk <woody.gilk@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,32 @@
# Okta Provider for OAuth 2.0 Client
[![Latest Version](https://img.shields.io/github/release/foxworth42/oauth2-okta.svg?style=flat-square)](https://github.com/foxworth42/oauth2-okta/releases)
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md)
[![Total Downloads](https://img.shields.io/packagist/dt/foxworth42/oauth2-okta.svg?style=flat-square)](https://packagist.org/packages/foxworth42/oauth2-okta)
[![Build Status](https://travis-ci.org/foxworth42/oauth2-okta.svg?branch=master)](https://travis-ci.org/foxworth42/oauth2-okta)
[![Code Coverage](https://img.shields.io/coveralls/foxworth42/oauth2-okta.svg)](https://coveralls.io/r/foxworth42/oauth2-okta)
This package provides Okta OAuth 2.0 support for the PHP League's [OAuth 2.0 Client](https://github.com/thephpleague/oauth2-client).
## Installation
To install, use composer:
```
composer require foxworth42/oauth2-okta
```
## Usage
Usage is the same as The League's OAuth client, using `\Foxworth42\OAuth2\Client\Provider\Okta` as the provider.
You must configure the Issuer URI as the `issuer` parameter. The issuer URI can be found in Okta's admin dashboard under API -> Authorization Servers.
## Testing
``` bash
$ ./vendor/bin/phpunit
```
## License
The MIT License (MIT). Please see [License File](https://github.com/foxworth42/oauth2-okta/blob/master/LICENSE) for more information.

View File

@ -0,0 +1,46 @@
{
"name": "foxworth42/oauth2-okta",
"type": "library",
"description": "Okta OAuth 2.0 Client Provider for The PHP League OAuth2-Client",
"license": "MIT",
"authors": [
{
"name": "Ed Walker",
"email": "github@foxwire.org"
}
],
"keywords": [
"oauth",
"oauth2",
"client",
"authorization",
"authentication",
"okta"
],
"minimum-stability": "stable",
"require": {
"php": ">=7.1.0",
"league/oauth2-client": "^2.0"
},
"require-dev": {
"phpunit/phpunit": "^7.0",
"php-coveralls/php-coveralls": "^2.1",
"squizlabs/php_codesniffer": "^3.4"
},
"autoload": {
"psr-4": {
"Foxworth42\\OAuth2\\Client\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Foxworth42\\OAuth2\\Client\\Test\\": "tests/"
}
},
"scripts": {
"test": "phpunit",
"check": "phpcs",
"lint": "phpcs",
"lint-fix": "phpcbf"
}
}

View File

@ -0,0 +1,36 @@
<?php
$provider = require __DIR__ . '/provider.php';
if (!empty($_GET['error'])) {
// Got an error, probably user denied access
exit('Got error: ' . htmlspecialchars($_GET['error'], ENT_QUOTES, 'UTF-8'));
} elseif (empty($_GET['code'])) {
// If we don't have an authorization code then get one
$authUrl = $provider->getAuthorizationUrl();
$_SESSION['oauth2state'] = $provider->getState();
header('Location: ' . $authUrl);
exit;
} elseif (empty($_GET['state']) || ($_GET['state'] !== $_SESSION['oauth2state'])) {
// State is invalid, possible CSRF attack in progress
unset($_SESSION['oauth2state']);
exit('Invalid state');
} else {
// Try to get an access token (using the authorization code grant)
$token = $provider->getAccessToken('authorization_code', [
'code' => $_GET['code']
]);
$_SESSION['token'] = serialize($token);
// Optional: Now you have a token you can look up a users profile data
header('Location: user.php');
}

View File

@ -0,0 +1,26 @@
<?php
require __DIR__ . '/../vendor/autoload.php';
use Foxworth42\OAuth2\Client\Provider\Okta;
// Replace these with your token settings
$clientId = 'your-client-id';
$clientSecret = 'your-client-secret';
$issuer = 'https://demo.okta.com/oauth2/default';
// Change this if you are not using the built-in PHP server
$redirectUri = 'http://localhost:8080/';
// Start the session
session_start();
// Initialize the provider
$provider = new Okta(compact('clientId', 'clientSecret', 'redirectUri', 'issuer'));
// No HTML for demo, prevents any attempt at XSS
header('Content-Type', 'text/plain');
return $provider;

View File

@ -0,0 +1,8 @@
<?php
$provider = require __DIR__ . '/provider.php';
unset($_SESSION['token'], $_SESSION['state']);
header('Location: /');

View File

@ -0,0 +1,4 @@
#!/bin/bash
php -S localhost:8080

View File

@ -0,0 +1,40 @@
<?php
$provider = require __DIR__ . '/provider.php';
if (isset($_GET['logout']) && $_GET['logout'] = 1) {
unset($_SESSION['token']);
}
if (!empty($_SESSION['token'])) {
$token = unserialize($_SESSION['token']);
}
if (empty($token)) {
header('Location: /');
exit;
}
try {
// We got an access token, let's now get the user's details
$userDetails = $provider->getResourceOwner($token);
// Use these details to create a new profile
printf('Hello %s!<br/>', $userDetails->getFirstname());
} catch (Exception $e) {
// Failed to get user details
exit('Something went wrong: ' . $e->getMessage());
}
// Use this to interact with an API on the users behalf
echo "Token is: <tt>", $token->getToken(), "</tt><br/>";
// Use this to get a new access token if the old one expires
echo "Refresh token is: <tt>", $token->getRefreshToken(), "</tt><br/>";
// Number of seconds until the access token will expire, and need refreshing
echo "Expires at ", date('r', $token->getExpires()), "<br/>";
// Allow the user to logout
echo '<a href="?logout=1">Logout</a><br/>';

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/squizlabs/php_codesniffer/phpcs.xsd">
<arg name="basepath" value="."/>
<arg name="cache" value=".phpcs-cache"/>
<arg name="colors"/>
<arg name="extensions" value="php"/>
<arg value="sp"/>
<rule ref="PSR2">
</rule>
<file>src/</file>
<file>test/</file>
</ruleset>

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
bootstrap="vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
>
<testsuites>
<testsuite name="all">
<directory>./test</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">src/</directory>
</whitelist>
</filter>
<logging>
<log type="coverage-text" target="php://stdout"/>
<log type="coverage-clover" target="build/logs/clover.xml"/>
<log type="coverage-html" target="build/coverage"/>
</logging>
</phpunit>

View File

@ -0,0 +1,105 @@
<?php
namespace Foxworth42\OAuth2\Client\Provider;
use League\OAuth2\Client\Provider\AbstractProvider;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use League\OAuth2\Client\Token\AccessToken;
use League\OAuth2\Client\Tool\BearerAuthorizationTrait;
use Psr\Http\Message\ResponseInterface;
class Okta extends AbstractProvider
{
use BearerAuthorizationTrait;
protected $issuer = '';
protected $apiVersion = 'v1';
public function getBaseApiUrl()
{
return $this->issuer . '/' . $this->apiVersion;
}
/**
* Get authorization url to begin OAuth flow
*
* @link https://developer.okta.com/docs/reference/api/oidc/#authorize
* @return string
*/
public function getBaseAuthorizationUrl()
{
return $this->getBaseApiUrl().'/authorize';
}
/**
* Get access token url to retrieve token
*
* @link https://developer.okta.com/docs/reference/api/oidc/#token
* @param array $params
*
* @return string
*/
public function getBaseAccessTokenUrl(array $params)
{
return $this->getBaseApiUrl().'/token';
}
/**
* Get provider url to fetch user details
*
* @link https://developer.okta.com/docs/reference/api/oidc/#userinfo
* @param AccessToken $token
*
* @return string
*/
public function getResourceOwnerDetailsUrl(AccessToken $token)
{
return $this->getBaseApiUrl().'/userinfo';
}
protected function getAuthorizationParameters(array $options)
{
return parent::getAuthorizationParameters($options);
}
protected function getDefaultScopes()
{
return [
'openid',
'email',
'profile'
];
}
protected function getScopeSeparator()
{
return ' ';
}
protected function checkResponse(ResponseInterface $response, $data)
{
// @codeCoverageIgnoreStart
if (empty($data['error'])) {
return;
}
// @codeCoverageIgnoreEnd
$code = $response->getStatusCode();
$error = $data['error'];
if (is_array($error)) {
$code = $error['code'];
$error = $error['message'];
}
throw new IdentityProviderException($error, $code, $data);
}
protected function createResourceOwner(array $response, AccessToken $token)
{
$user = new OktaUser($response);
return $user;
}
}

View File

@ -0,0 +1,118 @@
<?php
namespace Foxworth42\OAuth2\Client\Provider;
use League\OAuth2\Client\Provider\ResourceOwnerInterface;
class OktaUser implements ResourceOwnerInterface
{
/**
* @var array
*/
protected $response;
/**
* @param array $response
*/
public function __construct(array $response)
{
$this->response = $response;
}
public function getId()
{
return $this->response['sub'];
}
/**
* Get preferred display name.
*
* @return string
*/
public function getName()
{
return $this->response['name'];
}
/**
* Get preferred first name.
*
* @return string
*/
public function getFirstName()
{
return $this->response['given_name'];
}
/**
* Get preferred last name.
*
* @return string
*/
public function getLastName()
{
return $this->response['family_name'];
}
/**
* Get locale.
*
* @return string|null
*/
public function getLocale()
{
if (array_key_exists('locale', $this->response)) {
return $this->response['locale'];
}
return null;
}
/**
* Get email address.
*
* @return string|null
*/
public function getEmail()
{
if (array_key_exists('email', $this->response)) {
return $this->response['email'];
}
return null;
}
/**
* Get preferred username.
*
* @return string|null
*/
public function getPreferredUsername()
{
if (array_key_exists('preferred_username', $this->response)) {
return $this->response['preferred_username'];
}
return null;
}
/**
* Get timezone for user.
*
* @return string|null
*/
public function getZoneInfo()
{
if (array_key_exists('zoneinfo', $this->response)) {
return $this->response['zoneinfo'];
}
return null;
}
/**
* Get user data as an array.
*
* @return array
*/
public function toArray()
{
return $this->response;
}
}

View File

@ -0,0 +1,11 @@
root = true
[*]
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = space
indent_size = 4
[*.md]
indent_size = 2
trim_trailing_whitespace = false

View File

@ -0,0 +1,2 @@
vendor/
composer.lock

View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) Flagrow
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,45 @@
# Patreon provider for the OAuth2 Client
[![MIT license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/gravure/oauth2-patreon/blob/master/LICENSE.md)
[![Latest Stable Version](https://img.shields.io/packagist/v/gravure/oauth2-patreon.svg)](https://packagist.org/packages/gravure/oauth2-patreon)
[![Total Downloads](https://img.shields.io/packagist/dt/gravure/oauth2-patreon.svg)](https://packagist.org/packages/gravure/oauth2-patreon)
[![Donate](https://img.shields.io/badge/patreon-support-yellow.svg)](https://www.patreon.com/flagrow)
[![Join our Discord server](https://discordapp.com/api/guilds/240489109041315840/embed.png)](https://flagrow.io/join-discord)
This package provides Patreon OAuth 2.0 support for the PHP League's [OAuth 2.0 Client](https://github.com/thephpleague/oauth2-client).
## Installation
To install, use composer:
```bash
composer require gravure/oauth2-patreon
```
## Configuration
Usage is the same for every League OAuth client provider, using the following provider:
```php
Gravure\Patreon\Oauth\Provider\Patreon
```
## Support our work
We prefer to keep our work available to everyone.
In order to do so we rely on voluntary contributions on [Patreon](https://www.patreon.com/flagrow).
## Security
If you discover a security vulnerability within this package, please send an email to the Gravure team at security@gravure.io. All security vulnerabilities will be promptly addressed.
Please include as many details as possible to speed up this process.
## Links
- [Source code on GitHub](https://github.com/gravure/oauth2-patreon)
- [Changelog](https://github.com/gravure/oauth2-patreon/blob/master/CHANGELOG.md)
- [Report an issue](https://github.com/gravure/oauth2-patreon/issues)
- [Download via Packagist](https://packagist.org/packages/gravure/oauth2-patreon)
A package by [Gravure](https://gravure.io/).

View File

@ -0,0 +1,35 @@
{
"name": "gravure/oauth2-patreon",
"description": "Provides Patreon OAuth 2.0 support for PHP League's OAuth 2.0 Client.",
"keywords": [
"oauth2",
"league",
"patreon"
],
"license": "MIT",
"authors": [
{
"name": "Daniël Klabbers",
"email": "daniel@klabbers.email",
"homepage": "http://luceos.com"
}
],
"support": {
"issues": "https://github.com/gravure/oauth2-patreon/issues",
"source": "https://github.com/gravure/oauth2-patreon"
},
"require": {
"php": "5.6.* || >=7.0",
"league/oauth2-client": "^1.0 || ^2.0"
},
"autoload": {
"psr-4": {
"Gravure\\Patreon\\Oauth\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Gravure\\Patreon\\Oauth\\Tests\\": "tests/"
}
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace Gravure\Patreon\Oauth\Exceptions;
use InvalidArgumentException;
class InvalidResourceException extends InvalidArgumentException
{
}

View File

@ -0,0 +1,144 @@
<?php
namespace Gravure\Patreon\Oauth\Provider;
use Gravure\Patreon\Oauth\Resources\Factory;
use Gravure\Patreon\Oauth\Resources\Patron;
use League\OAuth2\Client\Provider\AbstractProvider;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use League\OAuth2\Client\Provider\ResourceOwnerInterface;
use League\OAuth2\Client\Token\AccessToken;
use League\OAuth2\Client\Tool\BearerAuthorizationTrait;
use Psr\Http\Message\ResponseInterface;
class Patreon extends AbstractProvider
{
use BearerAuthorizationTrait;
/**
* @var string Key used in the access token response to identify the resource owner.
*/
const ACCESS_TOKEN_RESOURCE_OWNER_ID = 'id';
/**
* @var string
*/
protected $apiBaseUrl = 'https://www.patreon.com/api/oauth2/';
/**
* @var string
*/
protected $oauthBaseUrl = 'https://www.patreon.com/oauth2/';
/**
* @var array
*/
protected $scopes = [
'users', 'pledges-to-me', 'my-campaign'
];
/**
* Returns the base URL for authorizing a client.
*
* Eg. https://oauth.service.com/authorize
*
* @return string
*/
public function getBaseAuthorizationUrl()
{
return $this->oauthBaseUrl . 'authorize';
}
/**
* Returns the base URL for requesting an access token.
*
* Eg. https://oauth.service.com/token
*
* @param array $params
* @return string
*/
public function getBaseAccessTokenUrl(array $params)
{
return $this->apiBaseUrl . 'token';
}
/**
* Returns the URL for requesting the resource owner's details.
*
* @param AccessToken $token
* @return string
*/
public function getResourceOwnerDetailsUrl(AccessToken $token)
{
return $this->apiBaseUrl . 'api/current_user';
}
protected function getAuthorizationHeaders($token = null)
{
return $token ? [
'Authorization' => 'Bearer ' . $token
] : [];
}
/**
* Returns the default scopes used by this provider.
*
* This should only be the scopes that are required to request the details
* of the resource owner, rather than all the available scopes.
*
* @return array
*/
protected function getDefaultScopes()
{
return ['users'];
}
/**
* Checks a provider response for errors.
*
* @throws IdentityProviderException
* @param ResponseInterface $response
* @param array|string $data Parsed response data
* @return void
*/
protected function checkResponse(ResponseInterface $response, $data)
{
$error = null;
if (isset($data['error'])) {
$error['detail'] = $data['error'];
$error['code'] = 401;
}
if (isset($data['errors']) && count($data['errors'])) {
$error = array_shift($data['errors']);
}
if ($error) {
throw new IdentityProviderException(
$error['detail'],
$error['code'],
$response
);
}
}
/**
* @return string
*/
protected function getScopeSeparator()
{
return ' ';
}
/**
* Generates a resource owner object from a successful resource owner
* details request.
*
* @param array $response
* @param AccessToken $token
* @return ResourceOwnerInterface|Patron
*/
protected function createResourceOwner(array $response, AccessToken $token)
{
return Factory::create((array) $response);
}
}

View File

@ -0,0 +1,79 @@
<?php
namespace Gravure\Patreon\Oauth\Resources;
use Gravure\Patreon\Oauth\Exceptions\InvalidResourceException;
class Factory
{
/**
* @var array
*/
protected static $mapping = [
'user' => Patron::class,
'pledge' => Pledge::class,
];
/**
* @param array $payload
* @return Resource
*/
public static function create(array $payload)
{
$data = array_key_exists('data', $payload) ? $payload['data'] : $payload;
$type = $data['type'];
$included = array_key_exists('included', $payload) ? $payload['included'] : [];
$relationships = array_key_exists('relationships', $payload) ? $payload['included'] : [];
if (false === array_key_exists($type, static::$mapping)) {
throw new InvalidResourceException("Resource type $type not mapped.");
}
$class = static::$mapping[$type];
/** @var Resource $resource */
$resource = new $class;
$resource->id = $data['id'];
$resource->type = $type;
$resource->attributes = $data['attributes'];
foreach ($relationships as $type => $relationship) {
if (!is_array($relationship['data'])) {
$relationship['data'] = (array) $relationship['data'];
}
$resource->relationships[$type] = [];
foreach ($relationship['data'] as $relation) {
if (! isset($relation['type'], $relation['id'])) {
continue;
}
array_push(
$resource->relationships[$type],
static::retrieveIncluded($relation['type'], $relation['id'], $included)
);
}
}
return $resource;
}
/**
* @param $type
* @param $id
* @param array $included
* @return null|void
*/
protected static function retrieveIncluded($type, $id, array $included = [])
{
foreach ($included as $resource) {
if ($resource['type'] === $type && $resource['id'] === $id) {
return static::create($resource);
}
}
return null;
}
}

View File

@ -0,0 +1,71 @@
<?php
namespace Gravure\Patreon\Oauth\Resources;
use League\OAuth2\Client\Provider\ResourceOwnerInterface;
/**
* @property string $about
* @property string $created
* @property int $discord_id
* @property string $email
* @property string $facebook
* @property string $facebook_id
* @property string $first_name
* @property string $last_name
* @property string $full_name
* @property int $gender
* @property bool $has_password
* @property string $thumb_url
* @property string $image_url
* @property bool $is_deleted
* @property bool $is_email_verified
* @property bool $is_nuked
* @property bool $is_suspended
* @property array $social_connections
* @property string $twitter
* @property string $url
* @property string $vanity
* @property string $youtube
*/
class Patron extends Resource implements ResourceOwnerInterface
{
/**
* Returns the identifier of the authorized resource owner.
*
* @return mixed
*/
public function getId()
{
return $this->id;
}
/**
* @return string
*/
public function getUsername()
{
return $this->attributes['vanity'];
}
/**
* @return string
*/
public function getAvatar()
{
return $this->attributes['image_url'];
}
/**
* Return all of the owner details available as an array.
*
* @return array
*/
public function toArray()
{
return array_merge([
'id' => $this->id,
'type' => $this->type,
], $this->attributes);
}
}

View File

@ -0,0 +1,8 @@
<?php
namespace Gravure\Patreon\Oauth\Resources;
class Pledge extends Resource
{
}

View File

@ -0,0 +1,36 @@
<?php
namespace Gravure\Patreon\Oauth\Resources;
class Resource
{
/**
* @var int
*/
public $id;
/**
* @var string
*/
public $type;
/**
* @var array
*/
public $attributes = [];
/**
* @var array
*/
public $relationships = [];
/**
* {@inheritdoc}
*/
public function __get($name)
{
return array_key_exists($name, $this->attributes) ?
$this->attributes[$name] :
null;
}
}

View File

@ -0,0 +1,50 @@
# Cache and logs (Symfony2)
/app/cache/*
/app/logs/*
!app/cache/.gitkeep
!app/logs/.gitkeep
# Email spool folder
/app/spool/*
# Cache, session files and logs (Symfony3)
/var/cache/*
/var/logs/*
/var/sessions/*
!var/cache/.gitkeep
!var/logs/.gitkeep
!var/sessions/.gitkeep
# Parameters
/app/config/parameters.yml
/app/config/parameters.ini
# Managed by Composer
/app/bootstrap.php.cache
/var/bootstrap.php.cache
/bin/*
!bin/console
!bin/symfony_requirements
/vendor/
# Assets and user uploads
/web/bundles/
/web/uploads/
# PHPUnit
/app/phpunit.xml
/phpunit.xml
# Build data
/build/
# Composer
/composer.lock
/composer.phar
# Backup entities generated with doctrine:generate:entities command
**/Entity/*~
# Embedded web-server pid file
/.web-server-pid
/nbproject/private/

View File

@ -0,0 +1,35 @@
filter:
excluded_paths: [test/*]
checks:
php:
code_rating: true
remove_extra_empty_lines: true
remove_php_closing_tag: true
remove_trailing_whitespace: true
fix_use_statements:
remove_unused: true
preserve_multiple: false
preserve_blanklines: true
order_alphabetically: true
fix_php_opening_tag: true
fix_linefeed: true
fix_line_ending: true
fix_identation_4spaces: true
fix_doc_comments: true
tools:
external_code_coverage:
timeout: 600
runs: 3
php_analyzer: true
php_code_coverage: false
php_code_sniffer:
config:
standard: PSR2
filter:
paths: ['src']
php_loc:
enabled: true
excluded_dirs: [vendor, test]
php_cpd:
enabled: true
excluded_dirs: [vendor, test]

View File

@ -0,0 +1,32 @@
language: php
sudo: false
php:
- 5.6
- 7.0
- 7.1
- 7.2
- hhvm
matrix:
include:
- php: 5.6
env: 'COMPOSER_FLAGS="--prefer-stable --prefer-lowest"'
allow_failures:
- php: 5.6
- php: hhvm
fast_finish: true
before_script:
- travis_retry composer self-update
- travis_retry composer install --no-interaction --prefer-dist
- travis_retry phpenv rehash
script:
- ./vendor/bin/phpcs --standard=psr2 src/
- ./vendor/bin/phpunit --coverage-text --coverage-clover=coverage.clover test/
after_script:
- wget https://scrutinizer-ci.com/ocular.phar
- php ocular.phar code-coverage:upload --format=php-clover coverage.clover

View File

@ -0,0 +1,40 @@
# Contributing
Contributions are **welcome** and will be fully **credited**.
We accept contributions via Pull Requests on [Github](https://github.com/mrjoops/oauth2-jira).
## Pull Requests
- **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](http://pear.php.net/package/PHP_CodeSniffer).
- **Add tests!** - Your patch won't be accepted if it doesn't have tests.
- **Document any change in behaviour** - Make sure the README and any other relevant documentation are kept up-to-date.
- **Consider our release cycle** - We try to follow SemVer. Randomly breaking public APIs is not an option.
- **Create topic branches** - Don't ask us to pull from your master branch.
- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.
- **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please squash them before submitting.
- **Ensure tests pass!** - Please run the tests (see below) before submitting your pull request, and make sure they pass. We won't accept a patch until all tests pass.
- **Ensure no coding standards violations** - Please run PHP Code Sniffer using the PSR-2 standard (see below) before submitting your pull request. A violation will cause the build to fail, so please make sure there are no violations. We can't accept a patch if the build fails.
## Running Tests
``` bash
$ ./vendor/bin/phpunit
```
## Running PHP Code Sniffer
``` bash
$ ./vendor/bin/phpcs src --standard=psr2 -sp
```
**Happy coding**!

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Alexandre Lahure
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,125 @@
# Jira Provider for OAuth 2.0 Client
[![Latest Version](https://img.shields.io/github/tag/mrjoops/oauth2-jira.svg?style=flat-square)](https://github.com/mrjoops/oauth2-jira/releases)
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md)
[![Build Status](https://img.shields.io/travis/mrjoops/oauth2-jira/develop.svg?style=flat-square)](https://travis-ci.org/mrjoops/oauth2-jira)
[![Coverage Status](https://img.shields.io/scrutinizer/coverage/g/mrjoops/oauth2-jira.svg?style=flat-square)](https://scrutinizer-ci.com/g/mrjoops/oauth2-jira/code-structure)
[![Quality Score](https://img.shields.io/scrutinizer/g/mrjoops/oauth2-jira.svg?style=flat-square)](https://scrutinizer-ci.com/g/mrjoops/oauth2-jira)
This package provides Jira OAuth 2.0 support for the PHP League's [OAuth 2.0 Client](https://github.com/thephpleague/oauth2-client).
## Installation
To install, use composer:
```
composer require mrjoops/oauth2-jira
```
## Usage
Usage is the same as The League's OAuth client, using `\Mrjoops\OAuth2\Client\Provider\Jira` as the provider.
### Authorization Code Flow
```php
$provider = new Mrjoops\OAuth2\Client\Provider\Jira([
'clientId' => '{jira-client-id}',
'clientSecret' => '{jira-client-secret}',
'redirectUri' => 'https://example.com/callback-url',
]);
if (!isset($_GET['code'])) {
// If we don't have an authorization code then get one
$authUrl = $provider->getAuthorizationUrl();
$_SESSION['oauth2state'] = $provider->getState();
header('Location: '.$authUrl);
exit;
// Check given state against previously stored one to mitigate CSRF attack
} elseif (empty($_GET['state']) || ($_GET['state'] !== $_SESSION['oauth2state'])) {
unset($_SESSION['oauth2state']);
exit('Invalid state');
} else {
// Try to get an access token (using the authorization code grant)
$token = $provider->getAccessToken('authorization_code', [
'code' => $_GET['code']
]);
// Optional: Now you have a token you can look up a users profile data
try {
// We got an access token, let's now get the user's details
$user = $provider->getResourceOwner($token);
// Use these details to create a new profile
printf('Hello %s!', $user->getNickname());
} catch (Exception $e) {
// Failed to get user details
exit('Oh dear...');
}
// Use this to interact with an API on the users behalf
echo $token->getToken();
}
```
### Managing Scopes
When creating your Jira authorization URL, you can specify the state and scopes your application may authorize.
```php
$options = [
'state' => 'OPTIONAL_CUSTOM_CONFIGURED_STATE',
'scope' => ['read:jira-user','read:jira-work] // array or string
];
$authorizationUrl = $provider->getAuthorizationUrl($options);
```
If neither are defined, the provider will utilize internal defaults.
At the time of authoring this documentation, the [following scopes are available](https://developer.atlassian.com/cloud/jira/platform/oauth-2-authorization-code-grants-3lo-for-apps/#implementing-oauth-2-0-authorization-code-grants).
- read:jira-user
- read:jira-work
- write:jira-work
- manage:jira-project
- manage:jira-configuration
### Jira Cloud API call
Since your Jira Cloud API URL vary, you can get it using the `getApiUrl()` method of the provider.
```php
$request = $provider->getAuthenticatedRequest(
\Mrjoops\OAuth2\Client\Provider\Jira::METHOD_GET,
$provider->getApiUrl().'/rest/api/3/myself',
$token
);
```
## Testing
``` bash
$ ./vendor/bin/phpunit
```
## Contributing
Please see [CONTRIBUTING](https://github.com/mrjoops/oauth2-jira/blob/develop/CONTRIBUTING.md) for details.
## Credits
- [Alexandre Lahure](https://github.com/mrjoops)
- [All Contributors](https://github.com/mrjoops/oauth2-jira/contributors)
## License
The MIT License (MIT). Please see [License File](https://github.com/mrjoops/oauth2-jira/blob/develop/LICENSE) for more information.

View File

@ -0,0 +1,38 @@
{
"name": "mrjoops/oauth2-jira",
"description": "Jira OAuth 2.0 support for the PHP League's OAuth 2.0 Client",
"type": "library",
"require": {
"league/oauth2-client": "^2.0"
},
"require-dev": {
"phpunit/phpunit": "^5.0",
"mockery/mockery": "^1.2",
"squizlabs/php_codesniffer": "^3.3"
},
"autoload": {
"psr-4": {
"Mrjoops\\OAuth2\\Client\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Mrjoops\\OAuth2\\Client\\Test\\": "test/src/"
}
},
"license": "MIT",
"authors": [
{
"name": "Alexandre Lahure",
"email": "alexandre@lahu.re"
}
],
"keywords": [
"oauth",
"oauth2",
"client",
"authorization",
"authorisation",
"jira"
]
}

Some files were not shown because too many files have changed in this diff Show More