Skip to content

Commit

Permalink
Getting ready for Packagist
Browse files Browse the repository at this point in the history
  • Loading branch information
Ekwav committed Jul 1, 2017
1 parent 6fba60e commit f0ea160
Show file tree
Hide file tree
Showing 11 changed files with 130 additions and 99 deletions.
67 changes: 42 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,46 +1,63 @@

# UF_OAUTH2-SERVER
Implementation of [league/oauth2-server](https://oauth2.thephpleague.com) in [userfrosting4](https://userfrosting.com)

## What does it do?
With this sprinkle you can securly conect your app to userfrosting with the OAuth2 authorization flow.
In short, this is the `This App wants to get access to ...` that you know from Google facebook and others for userfrosting.
To get access to some data you have to redirect the user to `YourDomain.com/authorize/{{AppID}}/token/{{scope}}` while AppID
is an unique identifier to your app (you can have as many as you want) that will be generated when you register your app.
With this sprinkle, you can securely connect your app to userfrosting with the OAuth2 authorization flow.
In short, this is the `This App wants to get access to ...` that you know from Google Facebook and others for Userfrosting.
To get access to some data you have to redirect the user to `YourDomain.com/authorize/{{AppID}}/token/{{scope}}` while AppID
is a unique identifier to your app (you can have as many as you want) that will be generated when you register your app.
And {{scope}} is a list of permissions for your app separated by `+` or <kbd>space</kbd>.
You can also replace `token` with `code` to optain an access code instead of an token, more on this [here](http://stackoverflow.com/questions/16321455),
but I would recomend using the token for people that just want to conect their unity3d game to their own server.
You can also replace `token` with `code` to obtain an access code instead of a token, more on this [here](http://stackoverflow.com/questions/16321455),
but I would recommend using the token for people that just want to connect their unity3d game to their own server.
And also for other people that are just getting started.


# Instalation
1.Download and copy the Sprinkle, or open it with github and clone it in your Sprinkle folder.
1.Add `api` to your `sprinkles.json`.
1.Run `composer update` in the folder `/app`.
1.Then you have to create a public and private key, we need them in order to encrypt the tokens.
# Installation
1. Download and copy the Sprinkle, or open it with GitHub and clone it in your Sprinkle folder.
2. Add `api` to your `sprinkles.json`.
3. Run `composer update` in your Userfrosting installation directory (rootfolder).
4. Then you have to create a public and private key, we need them in order to encrypt the tokens.
Navigate to `UF_OAUTH2-SERVER/src/OAuth2` open the terminal and run `openssl genrsa -out private.key 1024` _you can also replace 1024 with 2048 to generate a longer key_
Then you have to extract the public key from the private key with `openssl rsa -in private.key -pubout -out public.key`. More information on this [here](https://oauth2.thephpleague.com/installation/)
1.Open the terminal in `/migrations` and run `php uf-install` type `y` wait till it finishes and close the terminal.
5. Open the terminal in your root Userfrosting directory and run `php bakery bake` till it finishes and closes the terminal.
You can now create `Clients` aka Applications.
1.Open `YourDomain/apps` and continue there.
6. Open `YourDomain/apps` and continue there.

## It looks like this
![screenshot1](https://github.com/Ekwav/UF_OAUTH2-SERVER/tree/develop/screenshots/authorization_page.PNG)
![screenshot2](https://github.com/Ekwav/UF_OAUTH2-SERVER/tree/develop/screenshots/authorization_page_mobile.PNG)
![screenshot1](https://github.com/Ekwav/UF_OAUTH2-SERVER/tree/develop/screenshots/manage_apps.PNG)
![screenshot1](https://github.com/Ekwav/UF_OAUTH2-SERVER/blob/master/screenshots/authorization_page.PNG?raw=true)
![screenshot2](https://github.com/Ekwav/UF_OAUTH2-SERVER/blob/master/screenshots/authorization_page_mobile.PNG?raw=true)
![screenshot1](https://github.com/Ekwav/UF_OAUTH2-SERVER/blob/master/screenshots/manage_apps.PNG?raw=true)


## Important things to know
My sprinkle may contain bugs and errors of any type, if you find one, please report it in the issues tab.

If you need help, you can find me in the [userfrosting chat](https://chat.userfrosting.com).
If you need help, you can find me in the [userfrosting chat](https://chat.userfrosting.com/direct/Ekwav).

You have to add every API-endpoint to the csrf execlution list in `public/index.php`.
Modify line `49` to `51` to say something like:
-You have to add every API-endpoint to the csrf blacklist in your config.-
Currently (1.7.2017) this didn't work for me, for that I recommend you to create
your routes after the schema `remoteapi/CUSTOMNAME` to cover them automatically.
```
'csrf' => [
// A list of URL paths to ignore CSRF checks on
'blacklist' => [
"^remoteapi/userinfo",
"^remoteapi/anotherEndpoint"
]
```
$csrfBlacklist = [
$container->config['assets.raw.path'],
"/api/userinfo",
"/api/anotherEndpoint"
];```

Don't remove the `Powered by Coflnet` from the authorization page, it has to stay visable. You can modify everything else to fit your needs.
## Usage
Protect your API endpoints by adding `->add(new ResourceServerMiddleware($this->ci->ResourceServer));` to the routes you want to protect. Remember [adding it to the csrfBlacklist, or name it `/remoteapi/ENDPOINT`](https://github.com/Ekwav/UF_OAUTH2-SERVER/blob/master/README.md#important-things-to-know). Now that route is protected and can only be accessed by using POST with an `Authorization` header with the value `access token`.
Getting an access token is as easy as redirecting the user from your application to your server by opening a browser. The URL follows the schema `https://YOURDOMAIN.COM/authorize/APPID/token/SCOPES` you can find your APPID on the site `/apps` the scopes is an array of permissions you want to get separated by `space` or `+` (URL encoded `space`). The user can then review the requested permissions and `authorize`, `edit` or `deny` it. If the user authorizes the request he will be redirected to the URL specified on app creation with the token as a parameter. You then have to grab from the URL and save it to the device storage for further use.
Now you have the token on your user's device, you are able to send requests.
`POST` it to the server as a header with the key `Authorization` and everything should be fine.
But how do you know, what user is requesting data from you at the server side in your Userfrosting Controller? It turns out, that the access token is an encrypted JSON array aka `JSON Web Token` that contains all important information about the token. It stores:
1. The user_id
2. The app/client_id
3. The scopes
4. The expiration time. (the default is 4 weeks)
5. The access token ID. (Can be used for disabling it from the server side or tracking purposes)
For an example on how to use these look at the `getUserInfo()` function in the [ApiAuthController](https://github.com/Ekwav/UF_OAUTH2-SERVER/blob/master/src/Controller/ApiAuthController.php#L183)

Don't remove the `Powered by Coflnet` from the authorization page, it has to stay visible. You can modify everything else under MIT to fit your needs.
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "coflnet/api",
"name": "ekwav/uf_oauth2_server",
"type": "sprinkle",
"description": "Api module for UserFrosting.",
"description": "OAuth2 Api module for UserFrosting.",
"license" : "",
"authors" : [
{
Expand Down
48 changes: 31 additions & 17 deletions config/default.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,21 @@
* Sample site configuration file for UserFrosting. You should definitely set these values!
*
*/
return [
return [
'csrf' => [
// A list of url paths to ignore CSRF checks on
// URL paths will be matched against each regular expression in this list.
// Each regular expression should map to an array of methods.
// Regular expressions will be delimited with ~ in preg_match, so if you
// have routes with ~ in them, you must escape this character in your regex.
// Also, remember to use ^ when you only want to match the beginning of a URL path!
// See the docs https://learn.userfrosting.com/routes-and-controllers/client-input/csrf-guard#BlacklistingRoutes
'blacklist' => [
'api/userinfo' => [
'POST'
]
]
],
'throttles' => [
'guess_secret_attempt' => [
'method' => 'ip',
Expand All @@ -22,43 +36,43 @@
'method' => 'data', //app id
'interval' => 172800,
'delays' => [
1 => 3600,
1 => 3600,
2 => 7200, //2h
3 => 21600, //6h
4 => 43200, //12 hours
5 => 86400, //one day
6 => 172800, //two days
]
],
'login_from_outside_default' => [
'login_from_outside_default' => [
'method' => 'data', //app id
'interval' => 7200,
'delays' => [
10 => 60,
100 => 600,
500 => 3600,
1000 => 21600,
10 => 60,
100 => 600,
500 => 3600,
1000 => 21600,
]
],
'login_from_outside_trusted' => [
'login_from_outside_trusted' => [
'method' => 'data', //app id
'interval' => 7200,
'delays' => [
100 => 60,
1000 => 600,
5000 => 3600,
1000 => 21600,
100 => 60,
1000 => 600,
5000 => 3600,
1000 => 21600,
]
],
'oauth2_authorize_request' => [
'method' => 'data', //user id
'interval' => 7200,
'delays' => [
10 => 60,
20 => 300,
50 => 3600,
100 => 21600,
10 => 60,
20 => 300,
50 => 3600,
100 => 21600,
]
],
],
],
];
8 changes: 8 additions & 0 deletions locale/de_DE/translation.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@
"@TRANSLATION" => "E-mail",
"DESCRIPTION" => "Die E-mail deines Accounts."
],
"FULL_ACCESS" => [
"@TRANSLATION" => "Administratorischer Zugriff",
"DESCRIPTION" => "Diese App erhält Zugriff auf alles, worauf du auch Zugriff hast. Vergib diese Berechtigung mit bedacht!"
],
"USER_ID" => [
"@TRANSLATION" => "Nutzer Nummer",
"DESCRIPTION" => "Eine deinem Account zugeordnete Nutzernummer, die es ermöglicht deine Identität zu beweisen, du brauchst mindestens diese Brechtigung. Diese Nummer sagt nichts über dich aus!"
],
],
"CLIENT" => [
"NEW" => [
Expand Down
8 changes: 8 additions & 0 deletions locale/en_US/translation.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@
"BASIC" => [
"@TRANSLATION" => "Basic",
"DESCRIPTION" => "Gives acces to your userID, username and CloudSave."
],
"FULL_ACCESS" => [
"@TRANSLATION" => "FULL ACCESS",
"DESCRIPTION" => "This Application will get access to EVERYTHING you have access to. Be careful to who you grant this permission!"
],
"USER_ID" => [
"@TRANSLATION" => "UserID",
"DESCRIPTION" => "This id is assigned to your account and can be used to validate your identity. You need atleast this permission to authorize an app. This Number holds none of your personal information!"
]
],
"CLIENT" => [
Expand Down
6 changes: 3 additions & 3 deletions routes/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@
//$app->get('/oauth2/change', 'UserFrosting\Sprinkle\Api\Controller\ApiAuthController:oAuth2ChangeRequest');
$app->get('/apps', 'UserFrosting\Sprinkle\Api\Controller\ApiAuthController:renderClients')->add('authGuard');
$app->get('/app/new', 'UserFrosting\Sprinkle\Api\Controller\ApiAuthController:renderAddNewClient')->add('authGuard');
$app->get('/app/new/scope', 'UserFrosting\Sprinkle\Api\Controller\ApiAuthController:newScope');
//$app->get('/app/new/scope', 'UserFrosting\Sprinkle\Api\Controller\ApiAuthController:newScope');

$app->post('/authorize_vertify', 'UserFrosting\Sprinkle\Api\Controller\ApiAuthController:validateAuthRequest')->add('authGuard');
$app->post('/app/new', 'UserFrosting\Sprinkle\Api\Controller\ApiAuthController:addNewClient')->add('authGuard');

// This is a test api endpoint to try it :)
// IMPORTANT! YOU NEED TO ADD ALL YOUR API ENDPOINTS TO THE EXECLUTION LIST OF CSRF IN YOUR INDEX.PHP
// IMPORTANT! YOU NEED TO ADD ALL YOUR API ENDPOINTS TO THE CSRF.BLACKLIST IN YOUR config file
// OTHERWISE THE CSRF MIDDLEWARE WILL BLOCK THEM! You can find an example in the README
$app->post('/api/userinfo', 'UserFrosting\Sprinkle\Api\Controller\ApiAuthController:getUserInfo')->add(new ResourceServerMiddleware($this->ci->ResourceServer));

?>
?>
36 changes: 0 additions & 36 deletions src/Api.php

This file was deleted.

27 changes: 20 additions & 7 deletions src/Controller/ApiAuthController.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@
use Psr\Http\Message\ServerRequestInterface;
use UserFrosting\Sprinkle\Api\OAuth2\RefreshTokenRepository;
use UserFrosting\Sprinkle\Api\OAuth2\UserRepository;
use UserFrosting\Sprinkle\Api\OAuth2\UserEntity;
use UserFrosting\Sprinkle\Core\Facades\Debug;
use UserFrosting\Sprinkle\Api\Database\Models\Scopes;
use UserFrosting\Sprinkle\Api\Database\Models\OauthClients;
use UserFrosting\Fortress\RequestDataTransformer;
use UserFrosting\Fortress\RequestSchema;
use UserFrosting\Fortress\ServerSideValidator;
use UserFrosting\Fortress\Adapter\JqueryValidationAdapter;
use UserFrosting\Sprinkle\Account\Model\User;
use UserFrosting\Sprinkle\Account\Database\Models\User;

class ApiAuthController extends SimpleController
{
Expand Down Expand Up @@ -180,26 +181,38 @@ public function validateAuthRequest($request, $response, $args)
}


// This is an example Controller how to get user information with a token
public function getUserInfo($request, $response, $args)
{
// This is the Controller to get userinformation with a token
// First of all we get the user id and the scopes
$scopes = $request->getAttribute('oauth_scopes', []);
$user_id = $request->getAttribute('oauth_user_id', []);

// Now we will query the database with the user id
$user = User::where('id', $user_id)->first();

$response["user_id"] = $user->id;
// No matter what specified by the user we will always return his id.
$information["user_id"] = $user->id;

// If the user activated basic, we return user_name and his locale
if (in_array('basic', $scopes)) {
$response["user_name"] = $user->user_name;
$response["locale"] = $user->locale;
$information["user_name"] = $user->user_name;
$information["locale"] = $user->locale;
}
if (in_array('email', $scopes)) {
$response["email"] = $user->email;
$information["email"] = $user->email;
}
return $response->withJson($response);
// If the user granted full_access, we return everything we have
// or want, we shouldn't give out the password even if its encrypted.
if (in_array('full_access', $scopes)){
$information["user_name"] = $user->user_name;
$information["locale"] = $user->locale;
$information["email"] = $user->email;
}
return $response->withJson($information);
}


public function renderClients($request, $response, $args)
{
$apps = OauthClients::where('user_id', '=' , $this->ci->currentUser->id)->get()->toArray();
Expand Down
11 changes: 9 additions & 2 deletions src/Database/Migrations/v002/OauthScopes.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,15 @@ public function up()
]),
'full_access' => new Scopes([
'slug' => 'full_access',
'name' => 'OAUTH2.SCOPE.FULL',
'description' => 'OAUTH2.SCOPE.BASIC.DESCRIPTION',
'name' => 'OAUTH2.SCOPE.FULL_ACCESS',
'description' => 'OAUTH2.SCOPE.FULL_ACCESS.DESCRIPTION',
'permissions' => ''
])
,
'user_id' => new Scopes([
'slug' => 'user_id',
'name' => 'OAUTH2.SCOPE.USER_ID',
'description' => 'OAUTH2.SCOPE.USER_ID',
'permissions' => ''
])
];
Expand Down
6 changes: 3 additions & 3 deletions src/OAuth2/ClientRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class ClientRepository implements ClientRepositoryInterface
/**
* {@inheritdoc}
*/

public function __construct($clients)
{
$this->clients = $clients;
Expand All @@ -24,7 +24,7 @@ public function getClientEntity($clientIdentifier, $grantType, $clientSecret = n
$clients = [
$this->clients->public_id => [
'secret' => $this->clients->secret,// we normally don't need this, because the user will vertify himself
'name' => $this->clients-name,
'name' => $this->clients->name,
'redirect_uri' => $this->clients->redirect,
'is_confidential' => true,
],
Expand All @@ -46,4 +46,4 @@ public function getClientEntity($clientIdentifier, $grantType, $clientSecret = n
$client->setRedirectUri($clients[$clientIdentifier]['redirect_uri']);
return $client;
}
}
}
8 changes: 4 additions & 4 deletions templates/pages/oauth2_list_apps.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@
</div>
</td>
<td nowrap="nowrap">
{% if App.status == "1" %}
<i>Available</i>
{% if App.revoked == "0" %}
<i>Active</i>
{% else %}
<i>Disabled</i>
{% endif %}
Expand All @@ -44,9 +44,9 @@
</button>
<ul class="dropdown-menu dropdown-menu-right" role="menu">
<li>
<a href="#" class="js-displayForm" data-toggle="modal" data-formUrl="/Apps/{{app.id}}/edit"><i class="fa fa-edit"></i> Edit</a>
<a href="#" class="disabled js-displayForm" data-toggle="modal" data-formUrl="/Apps/{{app.id}}/edit"><i class="fa fa-edit"></i> Edit (not yet)</a>
</li>
<li>
<li>
<a href="{{site.uri.public}}/authorize/{{App.public_id}}/token/full_access"><i class="fa fa-user"></i> Optain a token</a>
</li>
<li>
Expand Down

0 comments on commit f0ea160

Please sign in to comment.