Merge pull request #3 from FoskyM/dev-oauth
Dev oauth merged & the first package will be released
This commit is contained in:
commit
f9e30d350e
40 changed files with 1259 additions and 80 deletions
32
extend.php
32
extend.php
|
@ -12,7 +12,10 @@
|
|||
namespace FoskyM\OAuthCenter;
|
||||
|
||||
use Flarum\Extend;
|
||||
use Flarum\Http\Middleware\AuthenticateWithHeader;
|
||||
use Flarum\Http\Middleware\CheckCsrfToken;
|
||||
use FoskyM\OAuthCenter\Middlewares\ResourceScopeMiddleware;
|
||||
use FoskyM\OAuthCenter\Middlewares\UnsetCsrfMiddleware;
|
||||
|
||||
return [
|
||||
(new Extend\Frontend('forum'))
|
||||
|
@ -26,9 +29,30 @@ return [
|
|||
new Extend\Locales(__DIR__.'/locale'),
|
||||
|
||||
(new Extend\Routes('forum'))
|
||||
->post('/oauth/authorize', 'oauth.authorize.post', Controllers\AuthorizeController::class),
|
||||
(new Extend\Routes('api'))
|
||||
->get('/oauth/clients', 'oauth.clients.list', Api\Controller\ListClientController::class),
|
||||
->post('/oauth/authorize', 'oauth.authorize.post', Controllers\AuthorizeController::class)
|
||||
->post('/oauth/token', 'oauth.token', Controllers\TokenController::class),
|
||||
|
||||
(new Extend\Middleware('api'))->add(ResourceScopeMiddleware::class),
|
||||
(new Extend\Routes('api'))
|
||||
->get('/oauth-clients', 'oauth.clients.list', Api\Controller\ListClientController::class)
|
||||
->post('/oauth-clients', 'oauth.clients.create', Api\Controller\CreateClientController::class)
|
||||
->get('/oauth-clients/{client_id}', 'oauth.clients.show', Api\Controller\ShowClientController::class)
|
||||
->patch('/oauth-clients/{id}', 'oauth.clients.update', Api\Controller\UpdateClientController::class)
|
||||
->delete('/oauth-clients/{id}', 'oauth.clients.delete', Api\Controller\DeleteClientController::class)
|
||||
|
||||
->get('/oauth-scopes', 'oauth.scopes.list', Api\Controller\ListScopeController::class)
|
||||
->post('/oauth-scopes', 'oauth.scopes.create', Api\Controller\CreateScopeController::class)
|
||||
->patch('/oauth-scopes/{id}', 'oauth.scopes.update', Api\Controller\UpdateScopeController::class)
|
||||
->delete('/oauth-scopes/{id}', 'oauth.scopes.delete', Api\Controller\DeleteScopeController::class)
|
||||
|
||||
->get('/user', 'user.show', Controllers\ApiUserController::class),
|
||||
|
||||
(new Extend\Settings)
|
||||
->serializeToForum('foskym-oauth-center.allow_implicit', 'foskym-oauth-center.allow_implicit', 'boolval')
|
||||
->serializeToForum('foskym-oauth-center.enforce_state', 'foskym-oauth-center.enforce_state', 'boolval')
|
||||
->serializeToForum('foskym-oauth-center.require_exact_redirect_uri', 'foskym-oauth-center.require_exact_redirect_uri', 'boolval'),
|
||||
|
||||
(new Extend\Middleware('api'))
|
||||
->insertAfter(AuthenticateWithHeader::class, ResourceScopeMiddleware::class),
|
||||
(new Extend\Middleware('forum'))
|
||||
->insertBefore(CheckCsrfToken::class, UnsetCsrfMiddleware::class),
|
||||
];
|
||||
|
|
BIN
js/dist/admin.js
generated
vendored
BIN
js/dist/admin.js
generated
vendored
Binary file not shown.
BIN
js/dist/admin.js.map
generated
vendored
BIN
js/dist/admin.js.map
generated
vendored
Binary file not shown.
BIN
js/dist/forum.js
generated
vendored
BIN
js/dist/forum.js
generated
vendored
Binary file not shown.
BIN
js/dist/forum.js.map
generated
vendored
BIN
js/dist/forum.js.map
generated
vendored
Binary file not shown.
|
@ -1,22 +1,100 @@
|
|||
import app from 'flarum/admin/app';
|
||||
import Page from 'flarum/common/components/Page';
|
||||
|
||||
import Button from 'flarum/common/components/Button';
|
||||
export default class ClientsPage extends Page {
|
||||
settingName = 'collapsible-posts.reasons';
|
||||
translationPrefix = 'foskym-oauth-center.admin.clients.';
|
||||
clients = [];
|
||||
oninit(vnode) {
|
||||
super.oninit(vnode);
|
||||
|
||||
app.store.find('oauth/clients').then(() => {
|
||||
this.fields = [
|
||||
'client_id',
|
||||
'client_secret',
|
||||
'redirect_uri',
|
||||
'grant_types',
|
||||
'scope',
|
||||
'client_name',
|
||||
'client_desc',
|
||||
'client_icon',
|
||||
'client_home'
|
||||
];
|
||||
|
||||
app.store.find('oauth-clients').then(r => {
|
||||
this.clients = r;
|
||||
this.fields.map(key => console.log(this.clients[0][key]))
|
||||
m.redraw();
|
||||
});
|
||||
}
|
||||
|
||||
view() {
|
||||
return (
|
||||
<div>
|
||||
<h2>Clients Page</h2>
|
||||
<div class={"OAuthCenter-clientsPage"}>
|
||||
{
|
||||
m('.Form-group', [
|
||||
m('table', [
|
||||
m('thead', m('tr', [
|
||||
this.fields.map(key => m('th', app.translator.trans(this.translationPrefix + key))),
|
||||
m('th'),
|
||||
])),
|
||||
m('tbody', [
|
||||
this.clients.map((client, index) => m('tr', [
|
||||
this.fields.map(key =>
|
||||
m('td', m('input.FormControl', {
|
||||
type: 'text',
|
||||
value: client[key]() || '',
|
||||
onchange: (event) => {
|
||||
this.saveClientInfo(index, key, event.target.value);
|
||||
},
|
||||
}))
|
||||
),
|
||||
m('td', Button.component({
|
||||
className: 'Button Button--icon',
|
||||
icon: 'fas fa-times',
|
||||
onclick: () => {
|
||||
this.clients[index].delete();
|
||||
this.clients.splice(index, 1);
|
||||
|
||||
},
|
||||
})),
|
||||
])),
|
||||
m('tr', m('td', {
|
||||
colspan: 9,
|
||||
}, Button.component({
|
||||
className: 'Button Button--block',
|
||||
onclick: () => {
|
||||
const client = app.store.createRecord('oauth-clients');
|
||||
const client_id = this.randomString(32);
|
||||
const client_secret = this.randomString(32);
|
||||
client.save({
|
||||
client_id: client_id,
|
||||
client_secret: client_secret,
|
||||
}).then(this.clients.push(client));
|
||||
},
|
||||
}, app.translator.trans(this.translationPrefix + 'add_button')))),
|
||||
]),
|
||||
]),
|
||||
])
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
randomString(len) {
|
||||
len = len || 32;
|
||||
let $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
let maxPos = $chars.length;
|
||||
let pwd = '';
|
||||
for (let i = 0; i < len; i++) {
|
||||
//0~32的整数
|
||||
pwd += $chars.charAt(Math.floor(Math.random() * (maxPos + 1)));
|
||||
}
|
||||
return pwd;
|
||||
}
|
||||
|
||||
saveClientInfo(index, key, value) {
|
||||
console.log(index, key, value);
|
||||
this.clients[index].save({
|
||||
[key]: value,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,118 @@
|
|||
import app from 'flarum/admin/app';
|
||||
import Page from 'flarum/common/components/Page';
|
||||
import Button from 'flarum/common/components/Button';
|
||||
import Select from 'flarum/common/components/Select';
|
||||
import Checkbox from 'flarum/common/components/Checkbox';
|
||||
|
||||
export default class ScopesPage extends Page {
|
||||
view() {
|
||||
return (
|
||||
<div>
|
||||
<h2>Scopes Page</h2>
|
||||
</div>
|
||||
);
|
||||
translationPrefix = 'foskym-oauth-center.admin.scopes.';
|
||||
scopes = [];
|
||||
|
||||
oninit(vnode) {
|
||||
super.oninit(vnode);
|
||||
|
||||
this.fields = [
|
||||
'scope',
|
||||
'resource_path',
|
||||
'method',
|
||||
'is_default',
|
||||
'scope_name',
|
||||
'scope_icon',
|
||||
'scope_desc'
|
||||
];
|
||||
|
||||
app.store.find('oauth-scopes').then(r => {
|
||||
this.scopes = r;
|
||||
this.fields.map(key => console.log(this.scopes[0][key]))
|
||||
m.redraw();
|
||||
});
|
||||
}
|
||||
|
||||
view() {
|
||||
return (
|
||||
<div class={"OAuthCenter-scopesPage"}>
|
||||
{
|
||||
m('.Form-group', [
|
||||
m('table', [
|
||||
m('thead', m('tr', [
|
||||
this.fields.map(key => m('th', app.translator.trans(this.translationPrefix + key))),
|
||||
m('th'),
|
||||
])),
|
||||
m('tbody', [
|
||||
this.scopes.map((scope, index) => m('tr', [
|
||||
this.fields.map(key =>
|
||||
m('td', key === 'method' ? Select.component({
|
||||
options: {
|
||||
'GET': 'GET',
|
||||
'POST': 'POST',
|
||||
'PUT': 'PUT',
|
||||
'DELETE': 'DELETE',
|
||||
'PATCH': 'PATCH',
|
||||
},
|
||||
value: scope[key]() || 'GET',
|
||||
disabled: scope.resource_path() === '/api/user' && key === 'method',
|
||||
onchange: (value) => {
|
||||
this.saveScopeInfo(index, key, value);
|
||||
},
|
||||
}) : key === 'is_default' ? Checkbox.component({
|
||||
state: scope[key]() === 1 || false,
|
||||
disabled: scope.resource_path() === '/api/user' && key === 'is_default',
|
||||
onchange: (checked) => {
|
||||
this.scopes[index].is_default((this.scopes[index].is_default() + 1) % 2)
|
||||
this.saveScopeInfo(index, key, checked ? 1 : 0);
|
||||
},
|
||||
}) : m('input.FormControl', {
|
||||
type: 'text',
|
||||
value: scope[key]() || '',
|
||||
disabled: scope.resource_path() === '/api/user' && key === 'resource_path',
|
||||
onchange: (event) => {
|
||||
this.saveScopeInfo(index, key, event.target.value);
|
||||
},
|
||||
}))
|
||||
),
|
||||
(scope.resource_path() !== '/api/user' && m('td', Button.component({
|
||||
className: 'Button Button--icon',
|
||||
icon: 'fas fa-times',
|
||||
onclick: () => {
|
||||
this.scopes[index].delete();
|
||||
this.scopes.splice(index, 1);
|
||||
},
|
||||
}))),
|
||||
])),
|
||||
m('tr', m('td', {
|
||||
colspan: 7,
|
||||
}, Button.component({
|
||||
className: 'Button Button--block',
|
||||
onclick: () => {
|
||||
const scope = app.store.createRecord('oauth-scopes');
|
||||
scope.save({
|
||||
'scope': 'Scope.' + this.randomString(8),
|
||||
'resource_path': '/api/' + this.randomString(4),
|
||||
'method': 'GET',
|
||||
}).then(this.scopes.push(scope));
|
||||
},
|
||||
}, app.translator.trans(this.translationPrefix + 'add_button')))),
|
||||
]),
|
||||
]),
|
||||
])
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
randomString(len) {
|
||||
len = len || 8;
|
||||
let $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
let maxPos = $chars.length;
|
||||
let str = '';
|
||||
for (let i = 0; i < len; i++) {
|
||||
//0~32的整数
|
||||
str += $chars.charAt(Math.floor(Math.random() * (maxPos + 1)));
|
||||
}
|
||||
return str;
|
||||
}
|
||||
saveScopeInfo(index, key, value) {
|
||||
this.scopes[index].save({
|
||||
[key]: value,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import Extend from 'flarum/common/extenders';
|
||||
import Client from "./models/Client";
|
||||
import Scope from "./models/Scope";
|
||||
|
||||
export default [
|
||||
new Extend.Store()
|
||||
.add('oauth-clients', Client),
|
||||
.add('oauth-clients', Client)
|
||||
.add('oauth-scopes', Scope),
|
||||
];
|
||||
|
|
11
js/src/common/models/Scope.js
Normal file
11
js/src/common/models/Scope.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
import Model from 'flarum/common/Model';
|
||||
|
||||
export default class Scope extends Model {
|
||||
scope = Model.attribute('scope');
|
||||
resource_path = Model.attribute('resource_path');
|
||||
method = Model.attribute('method');
|
||||
is_default = Model.attribute('is_default');
|
||||
scope_name = Model.attribute('scope_name');
|
||||
scope_icon = Model.attribute('scope_icon');
|
||||
scope_desc = Model.attribute('scope_desc');
|
||||
}
|
|
@ -4,8 +4,18 @@ import Page from 'flarum/common/components/Page';
|
|||
import IndexPage from 'flarum/forum/components/IndexPage';
|
||||
import LogInModal from 'flarum/forum/components/LogInModal';
|
||||
import extractText from 'flarum/common/utils/extractText';
|
||||
import Tooltip from 'flarum/common/components/Tooltip';
|
||||
import Button from 'flarum/common/components/Button';
|
||||
|
||||
export default class AuthorizePage extends IndexPage {
|
||||
params = [];
|
||||
client = null;
|
||||
scopes = null;
|
||||
client_scope = [];
|
||||
loading = true;
|
||||
is_authorized = false;
|
||||
submit_loading = false;
|
||||
|
||||
oninit(vnode) {
|
||||
super.oninit(vnode);
|
||||
if (!app.session.user) {
|
||||
|
@ -13,18 +23,205 @@ export default class AuthorizePage extends IndexPage {
|
|||
}
|
||||
|
||||
const params = m.route.param();
|
||||
|
||||
if (params.client_id == null || params.response_type == null || params.redirect_uri == null) {
|
||||
m.route.set('/');
|
||||
} else {
|
||||
this.params = params;
|
||||
app.store.find('oauth-clients', params.client_id).then(client => {
|
||||
if (client.length === 0) {
|
||||
m.route.set('/');
|
||||
} else {
|
||||
this.client = client[0];
|
||||
let uris = null;
|
||||
if (this.client.redirect_uri().indexOf(' ') > -1) {
|
||||
uris = this.client.redirect_uri().split(' ');
|
||||
} else {
|
||||
uris = [this.client.redirect_uri()];
|
||||
}
|
||||
|
||||
if (app.forum.attribute('foskym-oauth-center.require_exact_redirect_uri') && uris.indexOf(params.redirect_uri) == -1) {
|
||||
m.route.set('/');
|
||||
}
|
||||
if (app.forum.attribute('foskym-oauth-center.allow_implicit') && params.response_type == 'token') {
|
||||
m.route.set('/');
|
||||
}
|
||||
if (app.forum.attribute('foskym-oauth-center.enforce_state') && params.enforce_state == null) {
|
||||
m.route.set('/');
|
||||
}
|
||||
|
||||
app.store.find('oauth-scopes').then((scopes) => {
|
||||
this.scopes = scopes
|
||||
let scope = params.scope;
|
||||
|
||||
if (params.scope == null) {
|
||||
scope = this.client.scope();
|
||||
}
|
||||
|
||||
let scopes_temp = [];
|
||||
if (scope == null) {
|
||||
scopes_temp = [];
|
||||
} else if (scope.indexOf(' ') > -1) {
|
||||
scopes_temp = scope.split(' ');
|
||||
} else {
|
||||
scopes_temp = [scope];
|
||||
}
|
||||
|
||||
let default_scopes = [];
|
||||
this.scopes.map(scope => {
|
||||
let index = scopes_temp.indexOf(scope.scope());
|
||||
if (index > -1) {
|
||||
scopes_temp[index] = scope;
|
||||
} else {
|
||||
scopes_temp.slice(index, 1);
|
||||
}
|
||||
if (scope.is_default() === 1) {
|
||||
default_scopes.push(scope);
|
||||
}
|
||||
});
|
||||
|
||||
scopes_temp = scopes_temp.concat(default_scopes);
|
||||
|
||||
this.client_scope = scopes_temp.filter((scope, index) => scopes_temp.indexOf(scope) === index);
|
||||
console.log(this.client_scope);
|
||||
this.loading = false;
|
||||
m.redraw();
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setTitle() {
|
||||
app.setTitle(extractText(app.translator.trans('foskym-oauth-center.forum.page.title.authorize')));
|
||||
app.setTitleCount(0);
|
||||
}
|
||||
|
||||
view() {
|
||||
if (!this.client) {
|
||||
return '';
|
||||
}
|
||||
return (
|
||||
<div className="AuthorizePage">
|
||||
!this.loading && <div className="AuthorizePage">
|
||||
<div className="container">
|
||||
<div class="oauth-area">
|
||||
<div class="oauth-main">
|
||||
<div class="oauth-box oauth-header">
|
||||
<h2>{app.forum.attribute('title')}</h2>
|
||||
<p>
|
||||
{app.translator.trans('foskym-oauth-center.forum.authorize.access')} <a
|
||||
href={this.client.client_home()} target="_blank">{this.client.client_name()}</a>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
<div class="oauth-box oauth-body">
|
||||
|
||||
<div class="oauth-top">
|
||||
<img src={app.forum.attribute('faviconUrl')}/>
|
||||
<i class="fas fa-exchange-alt fa-2x"></i>
|
||||
<Tooltip text={this.client.client_desc()}>
|
||||
<img src={this.client.client_icon()}/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div class="oauth-scope-area">
|
||||
{
|
||||
this.client_scope.length > 0 && this.client_scope.map(scope => {
|
||||
let scope_info = null;
|
||||
this.scopes.map(s => {
|
||||
if (s.scope() === scope.scope()) {
|
||||
scope_info = s;
|
||||
}
|
||||
});
|
||||
if (scope_info == null) {
|
||||
return '';
|
||||
}
|
||||
return (
|
||||
<div class="oauth-scope">
|
||||
<div class="oauth-scope-left">
|
||||
{
|
||||
(scope_info.scope_icon().indexOf('fa-') > -1) ?
|
||||
<i class={"oauth-scope-object fa-2x " + scope_info.scope_icon()}
|
||||
style="margin-left:2px;color:#000"></i> :
|
||||
<img class="oauth-scope-object" src={scope_info.scope_icon()} style="width:32px"/>
|
||||
}
|
||||
</div>
|
||||
<div class="oauth-scope-body">
|
||||
<h6 class="oauth-scope-heading">
|
||||
{scope_info.scope_name()}
|
||||
</h6>
|
||||
<small>
|
||||
{
|
||||
scope_info.scope_desc()
|
||||
.replace('{client_name}', this.client.client_name())
|
||||
.replace('{user}', app.session.user.attribute('displayName'))
|
||||
}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
<form class="oauth-form" method="post" id="form" onsubmit={this.onsubmit.bind(this)}>
|
||||
{/* <input type="hidden" name="response_type" value={this.params.response_type}/>
|
||||
<input type="hidden" name="client_id" value={this.params.client_id}/>
|
||||
<input type="hidden" name="redirect_uri"
|
||||
value={this.params.redirect_uri}/>
|
||||
<input type="hidden" name="state" value={this.params.state}/>
|
||||
<input type="hidden" name="scope" value={this.params.scope}/>*/}
|
||||
<input type="hidden" name="is_authorized" value={this.is_authorized}/>
|
||||
<div style="display: flex; margin-top: 15px" class="oauth-form-item">
|
||||
<Button className="Button" type="submit" style="width: 50%;" onclick={this.deny.bind(this)}
|
||||
loading={this.submit_loading}>
|
||||
{app.translator.trans('foskym-oauth-center.forum.authorize.deny')}
|
||||
</Button>
|
||||
<Button className="Button Button--primary" type="submit" style="width: 50%;"
|
||||
onclick={this.agree.bind(this)} loading={this.submit_loading}>
|
||||
{app.translator.trans('foskym-oauth-center.forum.authorize.agree')}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
deny(e) {
|
||||
this.is_authorized = false;
|
||||
}
|
||||
agree(e) {
|
||||
this.is_authorized = true;
|
||||
}
|
||||
|
||||
onsubmit(e) {
|
||||
e.preventDefault();
|
||||
this.submit_loading = true;
|
||||
app.request({
|
||||
method: 'POST',
|
||||
url: '/oauth/authorize',
|
||||
body: {
|
||||
response_type: this.params.response_type,
|
||||
client_id: this.params.client_id,
|
||||
redirect_uri: this.params.redirect_uri,
|
||||
state: this.params.state,
|
||||
scope: this.params.scope,
|
||||
is_authorized: this.is_authorized,
|
||||
}
|
||||
}).then((params) => {
|
||||
let arr = []
|
||||
for (let k in params) {
|
||||
arr.push(`${k}=${params[k]}`)
|
||||
}
|
||||
let url = `${this.params.redirect_uri }?${arr.join('&')}`;
|
||||
window.location.href = url;
|
||||
});
|
||||
|
||||
// Some form handling logic here
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,20 +16,19 @@ app.initializers.add('foskym/flarum-oauth-center', () => {
|
|||
};
|
||||
extend(UserPage.prototype, 'navItems', function (items) {
|
||||
if (app.session.user && app.session.user.id() === this.user.id()) {
|
||||
items.add(
|
||||
'authorized',
|
||||
LinkButton.component(
|
||||
{
|
||||
href: app.route('user.authorized', { username: this.user.username() }),
|
||||
icon: 'fas fa-user-friends',
|
||||
},
|
||||
[
|
||||
app.translator.trans('foskym-oauth-center.forum.page.label.authorized'),
|
||||
// this.user.moderatorNoteCount() > 0 ? <span className="Button-badge">{this.user.moderatorNoteCount()}</span> : '',
|
||||
]
|
||||
),
|
||||
-110
|
||||
);
|
||||
// items.add(
|
||||
// 'authorized',
|
||||
// LinkButton.component(
|
||||
// {
|
||||
// href: app.route('user.authorized', { username: this.user.username() }),
|
||||
// icon: 'fas fa-user-friends',
|
||||
// },
|
||||
// [
|
||||
// app.translator.trans('foskym-oauth-center.forum.page.label.authorized'),
|
||||
// ]
|
||||
// ),
|
||||
// -110
|
||||
// );
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -24,6 +24,39 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.OAuthCenterPage-container {
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
.OAuthCenter-clientsPage, .OAuthCenter-scopesPage {
|
||||
table {
|
||||
width: 100%;
|
||||
|
||||
td, th {
|
||||
padding: 3px 5px;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.Checkbox {
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.FormControl {
|
||||
background: @body-bg;
|
||||
border-color: @control-bg;
|
||||
|
||||
// We set the same as Flarum default, but with more specificity
|
||||
&:focus,
|
||||
&.focus {
|
||||
border-color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@media (min-width: 992px) {
|
||||
.OAuthCenter {
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
@import url('./forum/oauth');
|
226
less/forum/oauth.less
Normal file
226
less/forum/oauth.less
Normal file
|
@ -0,0 +1,226 @@
|
|||
.oauth-area {
|
||||
display: block !important;
|
||||
position: relative;
|
||||
left: 0;
|
||||
top: 0;
|
||||
padding: 110px 0;
|
||||
min-height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.oauth-main {
|
||||
position: relative;
|
||||
width: 376px;
|
||||
margin: 0 auto;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0px 0px 15px 0px #bdbdbd;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.oauth-main::before {
|
||||
backdrop-filter: blur(20px);
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
background: hsla(0, 0%, 100%, .3);
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.oauth-box {
|
||||
padding: 20px;
|
||||
background-color: #f3f3f3;
|
||||
}
|
||||
|
||||
.oauth-header {
|
||||
backdrop-filter: blur(0);
|
||||
text-align: center;
|
||||
box-shadow: 0 5px 10px -5px #d2d2d2;
|
||||
border-radius: 12px 12px 0 0;
|
||||
}
|
||||
|
||||
.oauth-header h2 {
|
||||
margin-bottom: 8px;
|
||||
font-weight: 600;
|
||||
font-size: 40px;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.oauth-header p {
|
||||
font-weight: 400;
|
||||
font-size: 20px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.oauth-body {
|
||||
border-radius: 0 0 12px 12px;
|
||||
}
|
||||
|
||||
.oauth-body .oauth-form-item {
|
||||
position: relative;
|
||||
margin-bottom: 15px;
|
||||
clear: both;
|
||||
*zoom: 1;
|
||||
}
|
||||
|
||||
.oauth-body .oauth-form-item:after {
|
||||
content: '\20';
|
||||
clear: both;
|
||||
*zoom: 1;
|
||||
display: block;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.oauth-icon {
|
||||
position: absolute;
|
||||
left: 4px;
|
||||
top: 1px;
|
||||
width: auto;
|
||||
line-height: 35px;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
padding: 0 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
label:before {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.oauth-area {
|
||||
padding-top: 60px
|
||||
}
|
||||
|
||||
.oauth-main {
|
||||
width: 300px
|
||||
}
|
||||
|
||||
.oauth-box {
|
||||
padding: 10px
|
||||
}
|
||||
|
||||
.oauth-main::before {
|
||||
backdrop-filter: none;
|
||||
}
|
||||
|
||||
.oauth-header {
|
||||
// background-color: #fff;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
.oauth-area {
|
||||
padding-top: 0
|
||||
}
|
||||
|
||||
body {
|
||||
background: #f3f3f3 !important;
|
||||
}
|
||||
|
||||
.oauth-main {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.oauth-main::before {
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.oauth-header {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.oauth-box:last-child {
|
||||
box-shadow: 0 5px 10px -5px #d2d2d2;
|
||||
}
|
||||
}
|
||||
|
||||
.oauth-top {
|
||||
text-align: center;
|
||||
padding-bottom: 20px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.oauth-top img {
|
||||
width: 64px;
|
||||
border-radius: 50%;
|
||||
border: #4950578c solid 1px;
|
||||
box-shadow: 1px 0 0 0 #e8e8e8, 0 1px 0 0 #e8e8e8, 1px 1px 0 0 #e8e8e8, inset 1px 0 0 0 #e8e8e8, inset 0 1px 0 0 #e8e8e8;
|
||||
transition: all .3s;
|
||||
}
|
||||
|
||||
.oauth-top img:hover {
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, .3);
|
||||
}
|
||||
|
||||
.oauth-top i {
|
||||
top: -24px;
|
||||
position: relative;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
color: #111;
|
||||
}
|
||||
|
||||
.oauth-scope-area {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
overflow: auto;
|
||||
max-height: 350px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.oauth-scope {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.oauth-scope:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.oauth-scope, .oauth-scope-body {
|
||||
overflow: hidden;
|
||||
zoom: 1;
|
||||
}
|
||||
|
||||
.oauth-scope-body, .oauth-scope-left, .oauth-scope-right {
|
||||
display: table-cell;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.oauth-scope-left, .oauth-scope > .pull-left {
|
||||
padding-right: 10px;
|
||||
min-width: 42px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
img.oauth-scope-object {
|
||||
display: block;
|
||||
vertical-align: middle;
|
||||
border: 0;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.oauth-scope-body {
|
||||
width: 10000px;
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
.oauth-scope-heading {
|
||||
margin-top: 0;
|
||||
font-weight: 800;
|
||||
color: #382e2e;
|
||||
margin-block-end: 0;
|
||||
}
|
||||
|
||||
.oauth-scope-body small {
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
color: #aaa;
|
||||
}
|
|
@ -12,13 +12,23 @@ foskym-oauth-center:
|
|||
clients:
|
||||
client_id: 应用 ID
|
||||
client_secret: 应用密钥
|
||||
redirect_uri: 回调地址(多地址请用空格分割)
|
||||
grant_types: 授权类型(可空)
|
||||
scope: 权限(可空)
|
||||
name: 应用名称(可空)
|
||||
description: 应用描述(可空)
|
||||
icon: 应用图标地址(可空 可使用fontawesome图标)
|
||||
home: 主页地址(可空)
|
||||
redirect_uri: 回调地址
|
||||
grant_types: 授权类型
|
||||
scope: 权限
|
||||
client_name: 名称
|
||||
client_desc: 描述
|
||||
client_icon: 图标
|
||||
client_home: 主页
|
||||
add_button: 添加应用
|
||||
scopes:
|
||||
scope: 权限标识
|
||||
resource_path: 资源路径
|
||||
method: 请求方法
|
||||
is_default: 默认
|
||||
scope_name: 名称
|
||||
scope_icon: 图标
|
||||
scope_desc: 描述
|
||||
add_button: 添加权限
|
||||
|
||||
forum:
|
||||
page:
|
||||
|
@ -26,3 +36,7 @@ foskym-oauth-center:
|
|||
authorize: 授权
|
||||
label:
|
||||
authorized: 授权记录
|
||||
authorize:
|
||||
access: 授权访问
|
||||
agree: 授权
|
||||
deny: 拒绝
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of foskym/flarum-oauth-center.
|
||||
*
|
||||
* Copyright (c) 2023 FoskyM.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE.md
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Schema\Builder;
|
||||
|
||||
return [
|
||||
'up' => function (Builder $schema) {
|
||||
if (!$schema->hasTable('oauth_scopes')) {
|
||||
return;
|
||||
}
|
||||
$schema->getConnection()->table('oauth_scopes')->insert([
|
||||
'scope' => 'user.read',
|
||||
'resource_path' => '/api/user',
|
||||
'method' => 'GET',
|
||||
'is_default' => 1,
|
||||
'scope_name' => '获取用户信息',
|
||||
'scope_icon' => 'fas fa-user',
|
||||
'scope_desc' => '访问该用户({user})的个人信息等',
|
||||
]);
|
||||
},
|
||||
'down' => function (Builder $schema) {
|
||||
|
||||
},
|
||||
];
|
29
src/Api/Controller/CreateClientController.php
Normal file
29
src/Api/Controller/CreateClientController.php
Normal file
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace FoskyM\OAuthCenter\Api\Controller;
|
||||
|
||||
use Flarum\Api\Controller\AbstractCreateController;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Illuminate\Support\Arr;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Tobscure\JsonApi\Document;
|
||||
use FoskyM\OAuthCenter\Models\Client;
|
||||
use FoskyM\OAuthCenter\Api\Serializer\ClientSerializer;
|
||||
|
||||
class CreateClientController extends AbstractCreateController
|
||||
{
|
||||
public $serializer = ClientSerializer::class;
|
||||
protected function data(ServerRequestInterface $request, Document $document)
|
||||
{
|
||||
$actor = RequestUtil::getActor($request);
|
||||
$actor->assertAdmin();
|
||||
|
||||
$attributes = Arr::get($request->getParsedBody(), 'data.attributes');
|
||||
|
||||
return Client::create([
|
||||
'client_id' => Arr::get($attributes, 'client_id'),
|
||||
'client_secret' => Arr::get($attributes, 'client_secret'),
|
||||
'user_id' => $actor->id,
|
||||
]);
|
||||
}
|
||||
}
|
27
src/Api/Controller/CreateScopeController.php
Normal file
27
src/Api/Controller/CreateScopeController.php
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace FoskyM\OAuthCenter\Api\Controller;
|
||||
|
||||
use Flarum\Api\Controller\AbstractCreateController;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Illuminate\Support\Arr;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Tobscure\JsonApi\Document;
|
||||
use FoskyM\OAuthCenter\Models\Scope;
|
||||
use FoskyM\OAuthCenter\Api\Serializer\ScopeSerializer;
|
||||
|
||||
class CreateScopeController extends AbstractCreateController
|
||||
{
|
||||
public $serializer = ScopeSerializer::class;
|
||||
protected function data(ServerRequestInterface $request, Document $document)
|
||||
{
|
||||
$actor = RequestUtil::getActor($request);
|
||||
$actor->assertAdmin();
|
||||
|
||||
$attributes = Arr::get($request->getParsedBody(), 'data.attributes');
|
||||
|
||||
return Scope::create([
|
||||
'scope' => Arr::get($attributes, 'scope'),
|
||||
]);
|
||||
}
|
||||
}
|
26
src/Api/Controller/DeleteClientController.php
Normal file
26
src/Api/Controller/DeleteClientController.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace FoskyM\OAuthCenter\Api\Controller;
|
||||
|
||||
use Flarum\Api\Controller\AbstractDeleteController;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Illuminate\Support\Arr;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Tobscure\JsonApi\Document;
|
||||
use FoskyM\OAuthCenter\Models\Client;
|
||||
use FoskyM\OAuthCenter\Api\Serializer\ClientSerializer;
|
||||
|
||||
class DeleteClientController extends AbstractDeleteController
|
||||
{
|
||||
public $serializer = ClientSerializer::class;
|
||||
protected function delete(ServerRequestInterface $request)
|
||||
{
|
||||
$id = Arr::get($request->getQueryParams(), 'id');
|
||||
RequestUtil::getActor($request)
|
||||
->assertAdmin();
|
||||
|
||||
$client = Client::find($id);
|
||||
|
||||
$client->delete();
|
||||
}
|
||||
}
|
25
src/Api/Controller/DeleteScopeController.php
Normal file
25
src/Api/Controller/DeleteScopeController.php
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace FoskyM\OAuthCenter\Api\Controller;
|
||||
|
||||
use Flarum\Api\Controller\AbstractDeleteController;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Illuminate\Support\Arr;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Tobscure\JsonApi\Document;
|
||||
use FoskyM\OAuthCenter\Models\Scope;
|
||||
use FoskyM\OAuthCenter\Api\Serializer\ScopeSerializer;
|
||||
|
||||
class DeleteScopeController extends AbstractDeleteController
|
||||
{
|
||||
protected function delete(ServerRequestInterface $request)
|
||||
{
|
||||
$id = Arr::get($request->getQueryParams(), 'id');
|
||||
RequestUtil::getActor($request)
|
||||
->assertAdmin();
|
||||
|
||||
$scope = Scope::find($id);
|
||||
|
||||
$scope->delete();
|
||||
}
|
||||
}
|
|
@ -16,10 +16,8 @@ class ListClientController extends AbstractListController
|
|||
protected function data(ServerRequestInterface $request, Document $document)
|
||||
{
|
||||
$actor = RequestUtil::getActor($request);
|
||||
if (!$actor->isAdmin()) {
|
||||
return [];
|
||||
}
|
||||
$actor->assertAdmin();
|
||||
|
||||
return Client::get();
|
||||
return Client::all();
|
||||
}
|
||||
}
|
||||
|
|
23
src/Api/Controller/ListScopeController.php
Normal file
23
src/Api/Controller/ListScopeController.php
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
namespace FoskyM\OAuthCenter\Api\Controller;
|
||||
|
||||
use Flarum\Api\Controller\AbstractListController;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Illuminate\Support\Arr;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Tobscure\JsonApi\Document;
|
||||
use FoskyM\OAuthCenter\Models\Scope;
|
||||
use FoskyM\OAuthCenter\Api\Serializer\ScopeSerializer;
|
||||
|
||||
class ListScopeController extends AbstractListController
|
||||
{
|
||||
public $serializer = ScopeSerializer::class;
|
||||
protected function data(ServerRequestInterface $request, Document $document)
|
||||
{
|
||||
$actor = RequestUtil::getActor($request);
|
||||
$actor->assertAdmin();
|
||||
|
||||
return Scope::all();
|
||||
}
|
||||
}
|
26
src/Api/Controller/ShowClientController.php
Normal file
26
src/Api/Controller/ShowClientController.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace FoskyM\OAuthCenter\Api\Controller;
|
||||
|
||||
use Flarum\Api\Controller\AbstractListController;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Illuminate\Support\Arr;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Tobscure\JsonApi\Document;
|
||||
use FoskyM\OAuthCenter\Models\Client;
|
||||
use FoskyM\OAuthCenter\Api\Serializer\ClientPublicSerializer;
|
||||
|
||||
class ShowClientController extends AbstractListController
|
||||
{
|
||||
public $serializer = ClientPublicSerializer::class;
|
||||
protected function data(ServerRequestInterface $request, Document $document)
|
||||
{
|
||||
$client_id = Arr::get($request->getQueryParams(), 'client_id');
|
||||
RequestUtil::getActor($request)->assertRegistered();
|
||||
|
||||
$client = Client::where('client_id', $client_id)->get();
|
||||
|
||||
return $client;
|
||||
|
||||
}
|
||||
}
|
37
src/Api/Controller/UpdateClientController.php
Normal file
37
src/Api/Controller/UpdateClientController.php
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace FoskyM\OAuthCenter\Api\Controller;
|
||||
|
||||
use Flarum\Api\Controller\AbstractListController;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Illuminate\Support\Arr;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Tobscure\JsonApi\Document;
|
||||
use FoskyM\OAuthCenter\Models\Client;
|
||||
use FoskyM\OAuthCenter\Api\Serializer\ClientSerializer;
|
||||
|
||||
class UpdateClientController extends AbstractListController
|
||||
{
|
||||
public $serializer = ClientSerializer::class;
|
||||
protected function data(ServerRequestInterface $request, Document $document)
|
||||
{
|
||||
$actor = RequestUtil::getActor($request);
|
||||
$actor->assertAdmin();
|
||||
|
||||
$id = Arr::get($request->getQueryParams(), 'id');
|
||||
$client = Client::find($id);
|
||||
|
||||
$attributes = Arr::get($request->getParsedBody(), 'data.attributes', []);
|
||||
|
||||
collect(['client_id', 'client_secret', 'redirect_uri', 'grant_types', 'scope', 'client_name', 'client_desc', 'client_icon', 'client_home'])
|
||||
->each(function (string $attribute) use ($client, $attributes) {
|
||||
if (($val = Arr::get($attributes, $attribute)) !== null) {
|
||||
$client->$attribute = $val;
|
||||
}
|
||||
});
|
||||
|
||||
$client->save();
|
||||
|
||||
return $client;
|
||||
}
|
||||
}
|
37
src/Api/Controller/UpdateScopeController.php
Normal file
37
src/Api/Controller/UpdateScopeController.php
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace FoskyM\OAuthCenter\Api\Controller;
|
||||
|
||||
use Flarum\Api\Controller\AbstractShowController;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Illuminate\Support\Arr;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Tobscure\JsonApi\Document;
|
||||
use FoskyM\OAuthCenter\Models\Scope;
|
||||
use FoskyM\OAuthCenter\Api\Serializer\ScopeSerializer;
|
||||
|
||||
class UpdateScopeController extends AbstractShowController
|
||||
{
|
||||
public $serializer = ScopeSerializer::class;
|
||||
protected function data(ServerRequestInterface $request, Document $document)
|
||||
{
|
||||
$actor = RequestUtil::getActor($request);
|
||||
$actor->assertAdmin();
|
||||
|
||||
$id = Arr::get($request->getQueryParams(), 'id');
|
||||
$scope = Scope::find($id);
|
||||
|
||||
$attributes = Arr::get($request->getParsedBody(), 'data.attributes', []);
|
||||
|
||||
collect(['scope', 'resource_path', 'method', 'is_default', 'scope_name', 'scope_icon', 'scope_desc'])
|
||||
->each(function (string $attribute) use ($scope, $attributes) {
|
||||
if (($val = Arr::get($attributes, $attribute)) !== null) {
|
||||
$scope->$attribute = $val;
|
||||
}
|
||||
});
|
||||
|
||||
$scope->save();
|
||||
|
||||
return $scope;
|
||||
}
|
||||
}
|
35
src/Api/Serializer/ClientPublicSerializer.php
Normal file
35
src/Api/Serializer/ClientPublicSerializer.php
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
namespace FoskyM\OAuthCenter\Api\Serializer;
|
||||
|
||||
use Flarum\Api\Serializer\AbstractSerializer;
|
||||
use FoskyM\OAuthCenter\Models\Client;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class ClientPublicSerializer extends AbstractSerializer
|
||||
{
|
||||
protected $type = 'oauth-clients';
|
||||
|
||||
protected function getDefaultAttributes($model)
|
||||
{
|
||||
if (!($model instanceof Client)) {
|
||||
throw new InvalidArgumentException(
|
||||
get_class($this) . ' can only serialize instances of ' . Client::class
|
||||
);
|
||||
}
|
||||
|
||||
// See https://docs.flarum.org/extend/api.html#serializers for more information.
|
||||
|
||||
return [
|
||||
"id" => $model->id,
|
||||
"client_id" => $model->client_id,
|
||||
"redirect_uri" => $model->redirect_uri,
|
||||
"grant_types" => $model->grant_types,
|
||||
"scope" => $model->scope,
|
||||
"client_name" => $model->client_name,
|
||||
"client_icon" => $model->client_icon,
|
||||
"client_desc" => $model->client_desc,
|
||||
"client_home" => $model->client_home
|
||||
];
|
||||
}
|
||||
}
|
34
src/Api/Serializer/ScopeSerializer.php
Normal file
34
src/Api/Serializer/ScopeSerializer.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace FoskyM\OAuthCenter\Api\Serializer;
|
||||
|
||||
use Flarum\Api\Serializer\AbstractSerializer;
|
||||
use FoskyM\OAuthCenter\Models\Scope;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class ScopeSerializer extends AbstractSerializer
|
||||
{
|
||||
protected $type = 'oauth-scopes';
|
||||
|
||||
protected function getDefaultAttributes($model)
|
||||
{
|
||||
if (!($model instanceof Scope)) {
|
||||
throw new InvalidArgumentException(
|
||||
get_class($this) . ' can only serialize instances of ' . Scope::class
|
||||
);
|
||||
}
|
||||
|
||||
// See https://docs.flarum.org/extend/api.html#serializers for more information.
|
||||
|
||||
return [
|
||||
"id" => $model->id,
|
||||
"scope" => $model->scope,
|
||||
"resource_path" => $model->resource_path,
|
||||
"method" => $model->method,
|
||||
"is_default" => $model->is_default,
|
||||
"scope_name" => $model->scope_name,
|
||||
"scope_icon" => $model->scope_icon,
|
||||
"scope_desc" => $model->scope_desc,
|
||||
];
|
||||
}
|
||||
}
|
49
src/Controllers/ApiUserController.php
Normal file
49
src/Controllers/ApiUserController.php
Normal file
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of foskym/flarum-oauth-center.
|
||||
*
|
||||
* Copyright (c) 2023 FoskyM.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE.md
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
namespace FoskyM\OAuthCenter\Controllers;
|
||||
use Flarum\User\User;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use FoskyM\OAuthCenter\OAuth;
|
||||
use Illuminate\Support\Arr;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Laminas\Diactoros\Response\JsonResponse;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Flarum\Group\Group;
|
||||
|
||||
class ApiUserController implements RequestHandlerInterface
|
||||
{
|
||||
protected $settings;
|
||||
public function __construct(SettingsRepositoryInterface $settings)
|
||||
{
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$actor = RequestUtil::getActor($request);
|
||||
$actor = $actor->toArray();
|
||||
$data = [
|
||||
'id' => $actor['id'],
|
||||
'username' => $actor['username'],
|
||||
'nickname' => $actor['nickname'],
|
||||
'avatar_url' => $actor['avatar_url'],
|
||||
'email' => $actor['email'],
|
||||
'is_email_confirmed' => $actor['is_email_confirmed'],
|
||||
'joined_at' => $actor['joined_at'],
|
||||
'last_seen_at' => $actor['last_seen_at'],
|
||||
'discussion_count' => $actor['discussion_count'],
|
||||
'comment_count' => $actor['comment_count'],
|
||||
];
|
||||
return new JsonResponse($data);
|
||||
}
|
||||
}
|
|
@ -35,23 +35,24 @@ class AuthorizeController implements RequestHandlerInterface
|
|||
|
||||
$params = $request->getParsedBody();
|
||||
|
||||
$oauth = new OAuth();
|
||||
$oauth = new OAuth($this->settings);
|
||||
$server = $oauth->server();
|
||||
$request = $oauth->request()::createFromGlobals();
|
||||
$response = $oauth->response();
|
||||
|
||||
if (!$server->validateAuthorizeRequest($request, $response)) {
|
||||
$response->send();
|
||||
die;
|
||||
return new JsonResponse(json_decode($response->getResponseBody(), true));
|
||||
}
|
||||
|
||||
$is_authorized = (Arr::get($params, 'authorized', 'no') === 'yes');
|
||||
$is_authorized = Arr::get($params, 'is_authorized', 0);
|
||||
$server->handleAuthorizeRequest($request, $response, $is_authorized, $actor->id);
|
||||
if ($is_authorized) {
|
||||
// this is only here so that you get to see your code in the cURL request. Otherwise, we'd redirect back to the client
|
||||
/* $code = substr($response->getHttpHeader('Location'), strpos($response->getHttpHeader('Location'), 'code=')+5, 40);
|
||||
exit("SUCCESS! Authorization Code: $code");*/
|
||||
$code = substr($response->getHttpHeader('Location'), strpos($response->getHttpHeader('Location'), 'code=') + 5, 40);
|
||||
return new JsonResponse([
|
||||
'code' => $code
|
||||
]);
|
||||
}
|
||||
$response->send();
|
||||
|
||||
return new JsonResponse(json_decode($response->getResponseBody(), true));
|
||||
}
|
||||
}
|
||||
|
|
40
src/Controllers/TokenController.php
Normal file
40
src/Controllers/TokenController.php
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of foskym/flarum-oauth-center.
|
||||
*
|
||||
* Copyright (c) 2023 FoskyM.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE.md
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
namespace FoskyM\OAuthCenter\Controllers;
|
||||
use Flarum\User\User;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use FoskyM\OAuthCenter\OAuth;
|
||||
use Illuminate\Support\Arr;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Laminas\Diactoros\Response\JsonResponse;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Flarum\Group\Group;
|
||||
|
||||
class TokenController implements RequestHandlerInterface
|
||||
{
|
||||
protected $settings;
|
||||
public function __construct(SettingsRepositoryInterface $settings)
|
||||
{
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$oauth = new OAuth($this->settings);
|
||||
$server = $oauth->server();
|
||||
|
||||
$body = $server->handleTokenRequest($oauth->request()::createFromGlobals())
|
||||
->getResponseBody();
|
||||
return new JsonResponse(json_decode($body, true));
|
||||
}
|
||||
}
|
|
@ -4,50 +4,63 @@ namespace FoskyM\OAuthCenter\Middlewares;
|
|||
|
||||
use Flarum\Foundation\ErrorHandling\ExceptionHandler\IlluminateValidationExceptionHandler;
|
||||
use Flarum\Foundation\ErrorHandling\JsonApiFormatter;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Flarum\User\User;
|
||||
use FoskyM\OAuthCenter\OAuth;
|
||||
use FoskyM\OAuthCenter\Storage;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Laminas\Diactoros\Response\JsonResponse;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\Api\JsonApiResponse;
|
||||
use Tobscure\JsonApi\Document;
|
||||
use Tobscure\JsonApi\Exception\Handler\ResponseBag;
|
||||
|
||||
use FoskyM\OAuthCenter\Models\Scope;
|
||||
|
||||
class ResourceScopeMiddleware implements MiddlewareInterface
|
||||
{
|
||||
const TOKEN_PREFIX = 'Bearer ';
|
||||
protected $settings;
|
||||
public function __construct(SettingsRepositoryInterface $settings)
|
||||
{
|
||||
$this->settings = $settings;
|
||||
}
|
||||
public function process(Request $request, RequestHandlerInterface $handler): Response
|
||||
{
|
||||
$path = $request->getUri()->getPath();
|
||||
$token = Arr::get($request->getQueryParams(), 'access_token', '');
|
||||
if (!$request->getAttribute('originalUri')) {
|
||||
return $handler->handle($request);
|
||||
}
|
||||
|
||||
$headerLine = $request->getHeaderLine('authorization');
|
||||
|
||||
$parts = explode(';', $headerLine);
|
||||
|
||||
if (isset($parts[0]) && Str::startsWith($parts[0], self::TOKEN_PREFIX)) {
|
||||
$token = substr($parts[0], strlen(self::TOKEN_PREFIX));
|
||||
} else {
|
||||
$token = Arr::get($request->getQueryParams(), 'access_token', '');
|
||||
}
|
||||
$path = $request->getAttribute('originalUri')->getPath();
|
||||
|
||||
if ($token !== '' && $scope = Scope::get_path_scope($path)) {
|
||||
if (strtolower($request->getMethod()) === strtolower($scope->method)) {
|
||||
try {
|
||||
$oauth = new OAuth();
|
||||
$oauth = new OAuth($this->settings);
|
||||
$server = $oauth->server();
|
||||
$request = $oauth->request();
|
||||
if (!$server->verifyResourceRequest($request::createFromGlobals(), null, $scope->scope)) {
|
||||
$server->getResponse()->send('json');
|
||||
die;
|
||||
}
|
||||
/*$error = new ResponseBag('422', [
|
||||
[
|
||||
'status' => '422',
|
||||
'code' => 'validation_error',
|
||||
'source' => [
|
||||
'pointer' => $path,
|
||||
],
|
||||
'detail' => 'Yikes! The access token don\'t has the scope.',
|
||||
],
|
||||
]);
|
||||
$document = new Document();
|
||||
$document->setErrors($error->getErrors());
|
||||
$oauth_request = $oauth->request()::createFromGlobals();
|
||||
|
||||
return new JsonApiResponse($document, $error->getStatus());*/
|
||||
if (!$server->verifyResourceRequest($oauth_request, null, $scope->scope)) {
|
||||
return new JsonResponse(json_decode($server->getResponse()->getResponseBody(), true));
|
||||
}
|
||||
|
||||
$token = $server->getAccessTokenData($oauth_request);
|
||||
$actor = User::find($token['user_id']);
|
||||
|
||||
$request = RequestUtil::withActor($request, $actor);
|
||||
$request = $request->withAttribute('bypassCsrfToken', true);
|
||||
$request = $request->withoutAttribute('session');
|
||||
} catch (ValidationException $exception) {
|
||||
|
||||
$handler = resolve(IlluminateValidationExceptionHandler::class);
|
||||
|
|
35
src/Middlewares/UnsetCsrfMiddleware.php
Normal file
35
src/Middlewares/UnsetCsrfMiddleware.php
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
namespace FoskyM\OAuthCenter\Middlewares;
|
||||
|
||||
use Flarum\Foundation\ErrorHandling\ExceptionHandler\IlluminateValidationExceptionHandler;
|
||||
use Flarum\Foundation\ErrorHandling\JsonApiFormatter;
|
||||
use FoskyM\OAuthCenter\OAuth;
|
||||
use FoskyM\OAuthCenter\Storage;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\Api\JsonApiResponse;
|
||||
use Tobscure\JsonApi\Document;
|
||||
use Tobscure\JsonApi\Exception\Handler\ResponseBag;
|
||||
|
||||
use FoskyM\OAuthCenter\Models\Scope;
|
||||
class UnsetCsrfMiddleware implements MiddlewareInterface
|
||||
{
|
||||
public function process(Request $request, RequestHandlerInterface $handler): Response
|
||||
{
|
||||
$uri = [
|
||||
'/oauth/token',
|
||||
];
|
||||
$path = $request->getUri()->getPath();
|
||||
if (in_array($path, $uri)) {
|
||||
$request = $request->withAttribute('bypassCsrfToken', true);
|
||||
}
|
||||
|
||||
return $handler->handle($request);
|
||||
}
|
||||
}
|
|
@ -15,4 +15,5 @@ use Flarum\Database\AbstractModel;
|
|||
class AccessToken extends AbstractModel
|
||||
{
|
||||
protected $table = 'oauth_access_tokens';
|
||||
protected $guarded = [];
|
||||
}
|
||||
|
|
|
@ -15,4 +15,5 @@ use Flarum\Database\AbstractModel;
|
|||
class AuthorizationCode extends AbstractModel
|
||||
{
|
||||
protected $table = 'oauth_authorization_codes';
|
||||
protected $guarded = [];
|
||||
}
|
||||
|
|
|
@ -15,4 +15,16 @@ use Flarum\Database\AbstractModel;
|
|||
class Client extends AbstractModel
|
||||
{
|
||||
protected $table = 'oauth_clients';
|
||||
protected $guarded = [];
|
||||
|
||||
public static function build(string $client_id, string $client_secret, int $user_id)
|
||||
{
|
||||
$client = new static();
|
||||
|
||||
$client->client_id = $client_id;
|
||||
$client->client_secret = $client_secret;
|
||||
$client->user_id = $user_id;
|
||||
|
||||
return $client;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,4 +15,5 @@ use Flarum\Database\AbstractModel;
|
|||
class Jwt extends AbstractModel
|
||||
{
|
||||
protected $table = 'oauth_jwt';
|
||||
protected $guarded = [];
|
||||
}
|
||||
|
|
|
@ -15,4 +15,5 @@ use Flarum\Database\AbstractModel;
|
|||
class RefreshToken extends AbstractModel
|
||||
{
|
||||
protected $table = 'oauth_refresh_tokens';
|
||||
protected $guarded = [];
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ use Flarum\Database\AbstractModel;
|
|||
class Scope extends AbstractModel
|
||||
{
|
||||
protected $table = 'oauth_scopes';
|
||||
|
||||
protected $guarded = [];
|
||||
static public function get_path_scope($path = '')
|
||||
{
|
||||
return self::where('resource_path', 'like', $path . '%')->first();
|
||||
|
|
|
@ -36,9 +36,13 @@ class OAuth
|
|||
{
|
||||
return new Request;
|
||||
}
|
||||
|
||||
public function storage(): Storage
|
||||
{
|
||||
return new Storage;
|
||||
}
|
||||
public function server(): Server
|
||||
{
|
||||
|
||||
$storage = new Storage;
|
||||
$server = new Server($storage, array(
|
||||
'allow_implicit' => $this->settings->get('foskym-oauth-center.allow_implicit') == "1",
|
||||
|
|
|
@ -204,10 +204,10 @@ class Storage implements
|
|||
*/
|
||||
public function setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null, $code_challenge = null, $code_challenge_method = null)
|
||||
{
|
||||
if (func_num_args() > 6) {
|
||||
/*if (func_num_args() > 6) {
|
||||
// we are calling with an id token
|
||||
return call_user_func_array(array($this, 'setAuthorizationCodeWithIdToken'), func_get_args());
|
||||
}
|
||||
}*/
|
||||
|
||||
// convert expires to datestring
|
||||
$expires = date('Y-m-d H:i:s', $expires);
|
||||
|
@ -433,7 +433,7 @@ class Storage implements
|
|||
if ($result = Models\Scope::where('is_default', true)->get()) {
|
||||
$defaultScope = array_map(function ($row) {
|
||||
return $row['scope'];
|
||||
}, $result);
|
||||
}, $result->toArray());
|
||||
|
||||
return implode(' ', $defaultScope);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue