diff --git a/Provider.php b/Provider.php new file mode 100644 index 0000000..bf31974 --- /dev/null +++ b/Provider.php @@ -0,0 +1,30 @@ +setRaw($user['extra'])->map([ + 'id' => $user['id'], + 'nickname' => $user['nickname'], + 'name' => $user['name'], + 'email' => $user['email'], + 'avatar' => $user['avatar'], + ]); + } + + public static function additionalConfigKeys() + { + return ['profile_fields_selector', 'domain']; + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..f79fb7c --- /dev/null +++ b/README.md @@ -0,0 +1,65 @@ +# Usos + +```bash +composer require socialiteproviders/usos +``` + +## Installation & Basic Usage + +Please see the [Base Installation Guide](https://socialiteproviders.com/usage/), then follow the provider specific instructions below. + +### Add configuration to `config/services.php` + +```php +'usos' => [ + 'domain' => env('USOS_DOMAIN_URL'), // Because of every instance of USOS is self-hosted + 'client_id' => env('USOS_CLIENT_ID'), + 'client_secret' => env('USOS_CLIENT_SECRET'), + 'redirect' => env('USOS_REDIRECT_URI') +], +``` + +As a default, provider loads only necessary for Socialite data. If you need to get more data during authentication process, such as student status or phone number, you may add ```profile_fields_selector``` parameter: + +```php +'usos' => [ + // ... + 'profile_fields_selector' => env('USOS_PROFILE_FIELDS_SELECTOR'), + // ... +] +``` + +Some fields may be not available without ```scopes``` field specifying. + +### Add provider event listener + +Configure the package's listener to listen for `SocialiteWasCalled` events. + +Add the event to your `listen[]` array in `app/Providers/EventServiceProvider`. See the [Base Installation Guide](https://socialiteproviders.com/usage/) for detailed instructions. + +```php +protected $listen = [ + \SocialiteProviders\Manager\SocialiteWasCalled::class => [ + // ... other providers + 'SocialiteProviders\\Usos\\UsosExtendSocialite@handle', + ], +]; +``` + +### Usage + +You should now be able to use the provider like you would regularly use Socialite (assuming you have the facade installed): + +```php +return Socialite::driver('usos')->redirect(); +``` + +You also can use USOS scopes via Socialite ```scopes``` method: + +```php +return Socialite::driver('usos') + ->scopes(['personal', 'email']) + ->redirect(); +``` + +To get more information about ```scopes``` you may visit a documentation page at yours USOS instance, or at the [USOSapi Mother server webpage](https://apps.usos.edu.pl/developers/api/authorization/). diff --git a/Server.php b/Server.php new file mode 100644 index 0000000..fb28a69 --- /dev/null +++ b/Server.php @@ -0,0 +1,172 @@ +getDomain().'/services/oauth/request_token'; + } + + /** + * {@inheritdoc} + */ + public function urlAuthorization() + { + return $this->getDomain().'/services/oauth/authorize'; + } + + /** + * {@inheritdoc} + */ + public function urlTokenCredentials() + { + return $this->getDomain().'/services/oauth/access_token'; + } + + /** + * {@inheritdoc} + */ + public function urlUserDetails() + { + return $this->getDomain().'/services/users/user'.'?fields='.$this->getFieldsSelector(); + } + + /* + * Returns an instance domain, according to the environment configuration + */ + private function getDomain() + { + return $this->getConfig('domain'); + } + + /* + * Returns a fields selector for services/users/user method, which defines a list of fields, + * which will be returned from API endpoint. + */ + private function getFieldsSelector() + { + return $this->getConfig('profile_fields_selector', 'id|first_name|last_name|email|photo_urls'); + } + + /** + * {@inheritdoc} + */ + public function userDetails($data, TokenCredentials $tokenCredentials) + { + $user = new User(); + $user->id = $data['id']; + $user->nickname = $data['first_name'].' '.$data['last_name']; + $user->name = $data['first_name'].' '.$data['last_name']; + $user->avatar = $data['photo_urls']['50x50'] ?? null; + $user->email = $data['email'] ?? null; + + $user->extra = $data; + + return $user; + } + + /** + * Generate the OAuth protocol header for a temporary credentials + * request, based on the URI. + * + * @param string $uri + * + * @return string + */ + protected function temporaryCredentialsProtocolHeader($uri) + { + $parameters = array_merge($this->baseProtocolParameters(), array_merge([ + 'oauth_callback' => $this->clientCredentials->getCallbackUri(), + + ], ['scopes' => implode('|', $this->scopes)])); + + $parameters['oauth_signature'] = $this->signature->sign($uri, $parameters, 'POST'); + + return $this->normalizeProtocolParameters($parameters); + } + + public function getTemporaryCredentials() + { + $uri = $this->urlTemporaryCredentials(); + + $client = $this->createHttpClient(); + + $formParams = [ + 'scopes' => implode('|', $this->scopes), + ]; + + $header = $this->temporaryCredentialsProtocolHeader($uri); + $authorizationHeader = ['Authorization' => $header]; + $headers = $this->buildHttpClientHeaders($authorizationHeader); + + try { + $response = $client->post($uri, [ + 'headers' => $headers, + 'form_params' => $formParams, + ]); + + return $this->createTemporaryCredentials((string) $response->getBody()); + } catch (BadResponseException $e) { + $this->handleTemporaryCredentialsBadResponse($e); + } + + throw new CredentialsException('Failed to get temporary credentials'); + } + + /** + * {@inheritdoc} + */ + public function userUid($data, TokenCredentials $tokenCredentials) + { + return $data['id']; + } + + /** + * {@inheritdoc} + */ + public function userEmail($data, TokenCredentials $tokenCredentials) + { + return $data['email'] ?? null; + } + + /** + * {@inheritdoc} + */ + public function userScreenName($data, TokenCredentials $tokenCredentials) + { + return $data['screen_name'] ?? null; + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationUrl($temporaryIdentifier, array $options = []) + { + // Somebody can pass through an instance of temporary + // credentials, and we'll extract the identifier from there. + if ($temporaryIdentifier instanceof TemporaryCredentials) { + $temporaryIdentifier = $temporaryIdentifier->getIdentifier(); + } + $queryOauthToken = ['oauth_token' => $temporaryIdentifier]; + $parameters = (isset($this->parameters)) + ? array_merge($queryOauthToken, $this->parameters) + : $queryOauthToken; + + $url = $this->urlAuthorization(); + $queryString = http_build_query($parameters); + + return $this->buildUrl($url, $queryString); + } +} diff --git a/UsosExtendSocialite.php b/UsosExtendSocialite.php new file mode 100644 index 0000000..93bbb9f --- /dev/null +++ b/UsosExtendSocialite.php @@ -0,0 +1,18 @@ +extendSocialite('usos', Provider::class, Server::class); + } +} diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..e8787a1 --- /dev/null +++ b/composer.json @@ -0,0 +1,34 @@ +{ + "name": "socialiteproviders/usos", + "description": "USOS OAuth1 Provider for Laravel Socialite", + "license": "MIT", + "keywords": [ + "laravel", + "oauth", + "provider", + "socialite", + "USOS", + "Uniwersytecki System Obsługi Studiów" + ], + "authors": [ + { + "name": "Władysław Mostowicz", + "email": "mostovyv@uek.krakow.pl" + } + ], + "support": { + "issues": "https://github.com/socialiteproviders/providers/issues", + "source": "https://github.com/socialiteproviders/providers", + "docs": "https://socialiteproviders.com/usos" + }, + "require": { + "php": "^8.0", + "ext-json": "*", + "socialiteproviders/manager": "~4.0" + }, + "autoload": { + "psr-4": { + "SocialiteProviders\\Usos\\": "" + } + } +}