Migrate OAuth Admin settings to vue

Signed-off-by: Roeland Jago Douma <roeland@famdouma.nl>
This commit is contained in:
Roeland Jago Douma 2018-06-08 09:52:27 +02:00
parent 7b8063a242
commit d2d1e8e375
No known key found for this signature in database
GPG Key ID: F941078878347C0C
15 changed files with 6050 additions and 88 deletions

25
apps/oauth2/Makefile Normal file
View File

@ -0,0 +1,25 @@
all: dev-setup build-js-production
dev-setup: clean clean-dev npm-init
npm-init:
npm install
npm-update:
npm update
build-js:
npm run dev
build-js-production:
npm run build
watch-js:
npm run watch
clean:
rm -f js/oauth2.js
rm -f js/oauth2.map
clean-dev:
rm -rf node_modules

View File

@ -23,13 +23,18 @@ return [
'routes' => [
[
'name' => 'Settings#addClient',
'url' => '/settings',
'url' => '/clients',
'verb' => 'POST',
],
[
'name' => 'Settings#getClients',
'url' => '/clients',
'verb' => 'GET',
],
[
'name' => 'Settings#deleteClient',
'url' => '/clients/{id}/delete',
'verb' => 'POST'
'url' => '/clients/{id}',
'verb' => 'DELETE'
],
[
'name' => 'LoginRedirector#authorize',

113
apps/oauth2/js-src/App.vue Normal file
View File

@ -0,0 +1,113 @@
<!--
- @copyright Copyright (c) 2018 Roeland Jago Douma <roeland@famdouma.nl>
-
- @author Roeland Jago Douma <roeland@famdouma.nl>
-
- @license GNU AGPL version 3 or any later version
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-->
<template>
<div id="oauth2" class="section">
<h2>{{ t('oauth2', 'OAuth 2.0 clients') }}</h2>
<p class="settings-hint">{{ t('oauth2', 'OAuth 2.0 allows external services to request access to {instanceName}.', { instanceName: oc_defaults.name}) }}</p>
<table class="grid">
<thead>
<tr>
<th id="headerName" scope="col">{{ t('oauth2', 'Name') }}</th>
<th id="headerRedirectUri" scope="col">{{ t('oauth2', 'Redirection URI') }}</th>
<th id="headerClientIdentifier" scope="col">{{ t('oauth2', 'Client Identifier') }}</th>
<th id="headerSecret" scope="col">{{ t('oauth2', 'Secret') }}</th>
<th id="headerRemove">&nbsp;</th>
</tr>
</thead>
<tbody>
<OAuthItem v-for="client in clients"
:key="client.id"
:client="client"
@delete="deleteClient"
/>
</tbody>
</table>
<br/>
<h3>{{ t('oauth2', 'Add client') }}</h3>
<form @submit.prevent="addClient">
<input type="text" id="name" name="name" :placeholder="t('oauth2', 'Name')" v-model="newClient.name">
<input type="url" id="redirectUri" name="redirectUri" :placeholder="t('oauth2', 'Redirection URI')" v-model="newClient.redirctUri">
<input type="submit" class="button" :value="t('oauth2', 'Add')">
</form>
</div>
</template>
<script>
import axios from 'axios';
import OAuthItem from './components/OAuthItem';
export default {
name: 'App',
components: {
OAuthItem
},
data: function() {
return {
clients: [],
newClient: {
name: '',
redirctUri: ''
}
};
},
beforeMount: function() {
let requestToken = OC.requestToken;
let tokenHeaders = { headers: { requesttoken: requestToken } };
axios.get(OC.generateUrl('apps/oauth2/clients'), tokenHeaders)
.then((response) => {
this.clients = response.data;
});
},
methods: {
deleteClient(id) {
let requestToken = OC.requestToken;
let tokenHeaders = { headers: { requesttoken: requestToken } };
axios.delete(OC.generateUrl('apps/oauth2/clients/{id}', {id: id}), tokenHeaders)
.then((response) => {
this.clients = this.clients.filter(client => client.id !== id);
});
},
addClient() {
let requestToken = OC.requestToken;
let tokenHeaders = { headers: { requesttoken: requestToken } };
axios.post(
OC.generateUrl('apps/oauth2/clients'),
{
name: this.newClient.name,
redirectUri: this.newClient.redirctUri
},
tokenHeaders)
.then((response) => {
this.clients.push(response.data)
this.newClient.name = '';
this.newClient.redirctUri = '';
}
);
}
},
}
</script>

View File

@ -0,0 +1,66 @@
<!--
- @copyright Copyright (c) 2018 Roeland Jago Douma <roeland@famdouma.nl>
-
- @author Roeland Jago Douma <roeland@famdouma.nl>
-
- @license GNU AGPL version 3 or any later version
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-->
<template>
<tr>
<td>{{name}}</td>
<td>{{redirectUri}}</td>
<td><code>{{clientId}}</code></td>
<td><code>{{renderedSecret}}</code><a class='icon-toggle has-tooltip' :title="t('oauth2', 'Show client secret')" @click="toggleSecret">SHOW SECRET</a></td>
<td class="action-column"><span><a class="icon-delete has-tooltip" :title="t('oauth2', 'Delete')" @click="$emit('delete', id)">DELETE</a></span></td>
</tr>
</template>
<script>
export default {
name: 'OAuthItem',
props: {
client: {
type: Object,
required: true
}
},
data: function() {
return {
id: this.client.id,
name: this.client.name,
redirectUri: this.client.redirectUri,
clientId: this.client.clientId,
clientSecret: this.client.clientSecret,
renderSecret: false,
};
},
computed: {
renderedSecret: function() {
if (this.renderSecret) {
return this.clientSecret;
} else {
return '****';
}
}
},
methods: {
toggleSecret() {
this.renderSecret = !this.renderSecret;
}
}
}
</script>

View File

@ -0,0 +1,35 @@
/**
* @copyright Copyright (c) 2018 Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import Vue from 'vue';
import App from './App.vue';
Vue.prototype.t = t;
Vue.prototype.oc_defaults = oc_defaults;
Vue.prototype.OC = OC;
const app = new Vue({
render: h => h(App)
}).$mount('#oauth2');
export { app };

View File

@ -0,0 +1,28 @@
const path = require('path')
const { VueLoaderPlugin } = require('vue-loader');
module.exports = {
entry: path.join(__dirname, 'main.js'),
output: {
path: path.resolve(__dirname, '../js'),
publicPath: '/js',
filename: 'oauth2.js'
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
}
]
},
plugins: [
new VueLoaderPlugin()
],
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js'
},
extensions: ['*', '.js', '.vue', '.json']
}
}

View File

@ -0,0 +1,12 @@
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'development',
devServer: {
historyApiFallback: true,
noInfo: true,
overlay: true
},
devtool: '#eval-source-map',
})

View File

@ -0,0 +1,7 @@
const merge = require('webpack-merge')
const common = require('./webpack.common.js')
module.exports = merge(common, {
mode: 'production',
devtool: '#source-map'
})

37
apps/oauth2/js/oauth2.js Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,15 +0,0 @@
$(document).ready(function () {
$('.show-oauth-credentials').click(function() {
var row = $(this).parent();
var code = $(row).find('code');
if(code.text() === '****') {
code.text(row.data('value'));
$(this).css('opacity', 0.9);
} else {
code.text('****');
$(this).css('opacity', 0.3);
}
})
});

View File

@ -26,6 +26,7 @@ use OCA\OAuth2\Db\AccessTokenMapper;
use OCA\OAuth2\Db\Client;
use OCA\OAuth2\Db\ClientMapper;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Http\RedirectResponse;
use OCP\IRequest;
use OCP\IURLGenerator;
@ -54,7 +55,7 @@ class SettingsController extends Controller {
* @param AccessTokenMapper $accessTokenMapper
* @param DefaultTokenMapper $defaultTokenMapper
*/
public function __construct($appName,
public function __construct(string $appName,
IRequest $request,
IURLGenerator $urlGenerator,
ClientMapper $clientMapper,
@ -70,31 +71,49 @@ class SettingsController extends Controller {
$this->defaultTokenMapper = $defaultTokenMapper;
}
/**
* @param string $name
* @param string $redirectUri
* @return RedirectResponse
*/
public function addClient($name,
$redirectUri) {
public function addClient(string $name,
string $redirectUri): JSONResponse {
$client = new Client();
$client->setName($name);
$client->setRedirectUri($redirectUri);
$client->setSecret($this->secureRandom->generate(64, self::validChars));
$client->setClientIdentifier($this->secureRandom->generate(64, self::validChars));
$this->clientMapper->insert($client);
return new RedirectResponse($this->urlGenerator->getAbsoluteURL('/index.php/settings/admin/security'));
$client = $this->clientMapper->insert($client);
$result = [
'id' => $client->getId(),
'name' => $client->getName(),
'redirectUri' => $client->getRedirectUri(),
'clientId' => $client->getClientIdentifier(),
'clientSecret' => $client->getSecret(),
];
return new JSONResponse($result);
}
/**
* @param int $id
* @return RedirectResponse
*/
public function deleteClient($id) {
public function deleteClient(int $id): JSONResponse {
$client = $this->clientMapper->getByUid($id);
$this->accessTokenMapper->deleteByClientId($id);
$this->defaultTokenMapper->deleteByName($client->getName());
$this->clientMapper->delete($client);
return new RedirectResponse($this->urlGenerator->getAbsoluteURL('/index.php/settings/admin/security'));
return new JSONResponse([]);
}
public function getClients(): JSONResponse {
$clients = $this->clientMapper->getClients();
$result = [];
foreach ($clients as $client) {
$result[] = [
'id' => $client->getId(),
'name' => $client->getName(),
'redirectUri' => $client->getRedirectUri(),
'clientId' => $client->getClientIdentifier(),
'clientSecret' => $client->getSecret(),
];
}
return new JSONResponse($result);
}
}

View File

@ -43,9 +43,7 @@ class Admin implements ISettings {
return new TemplateResponse(
'oauth2',
'admin',
[
'clients' => $this->clientMapper->getClients(),
],
[],
''
);
}

5650
apps/oauth2/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

31
apps/oauth2/package.json Normal file
View File

@ -0,0 +1,31 @@
{
"name": "oauth2",
"version": "1.0.0",
"description": "OAuth2 setup",
"author": "Roeland Jago Douma <roeland@famdouma.nl>",
"license": "AGPL-3.0-or-later",
"main": "init.js",
"directories": {
"lib": "lib",
"test": "tests"
},
"scripts": {
"dev": "webpack --config js-src/webpack.dev.js",
"watch": "webpack --progress --watch --config js-src/webpack.dev.js",
"build": "webpack --progress --hide-modules --config js-src/webpack.prod.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"dependencies": {
"axios": "^0.18.0",
"vue": "^2.5.16"
},
"devDependencies": {
"css-loader": "^0.28.11",
"file-loader": "^1.1.11",
"vue-loader": "^15.2.4",
"vue-template-compiler": "^2.5.16",
"webpack": "^4.11.1",
"webpack-cli": "^3.0.3",
"webpack-merge": "^4.1.2"
}
}

View File

@ -19,58 +19,9 @@
*
*/
$urlGenerator = \OC::$server->getURLGenerator();
$themingDefaults = \OC::$server->getThemingDefaults();
script('oauth2', 'setting-admin');
script('oauth2', 'oauth2');
style('oauth2', 'setting-admin');
/** @var array $_ */
/** @var \OCA\OAuth2\Db\Client[] $clients */
$clients = $_['clients'];
?>
<div id="oauth2" class="section">
<h2><?php p($l->t('OAuth 2.0 clients')); ?></h2>
<p class="settings-hint"><?php p($l->t('OAuth 2.0 allows external services to request access to %s.', [$themingDefaults->getName()])); ?></p>
<table class="grid">
<thead>
<tr>
<th id="headerName" scope="col"><?php p($l->t('Name')); ?></th>
<th id="headerRedirectUri" scope="col"><?php p($l->t('Redirection URI')); ?></th>
<th id="headerClientIdentifier" scope="col"><?php p($l->t('Client Identifier')); ?></th>
<th id="headerSecret" scope="col"><?php p($l->t('Secret')); ?></th>
<th id="headerRemove">&nbsp;</th>
</tr>
</thead>
<tbody>
<?php
$imageUrl = $urlGenerator->imagePath('core', 'actions/toggle.svg');
foreach ($clients as $client) {
?>
<tr>
<td><?php p($client->getName()); ?></td>
<td><?php p($client->getRedirectUri()); ?></td>
<td><code><?php p($client->getClientIdentifier()); ?></code></td>
<td data-value="<?php p($client->getSecret()); ?>"><code>****</code><img class='show-oauth-credentials' src="<?php p($imageUrl); ?>"/></td>
<td>
<form id="form-inline" class="delete" action="<?php p($urlGenerator->linkToRoute('oauth2.Settings.deleteClient', ['id' => $client->getId()])); ?>" method="POST">
<input type="hidden" name="requesttoken" value="<?php p($_['requesttoken']) ?>" />
<input type="submit" class="button icon-delete" value="">
</form>
</td>
</tr>
<?php } ?>
</tbody>
</table>
<br/>
<h3><?php p($l->t('Add client')); ?></h3>
<form action="<?php p($urlGenerator->linkToRoute('oauth2.Settings.addClient')); ?>" method="POST">
<input type="text" id="name" name="name" placeholder="<?php p($l->t('Name')); ?>">
<input type="url" id="redirectUri" name="redirectUri" placeholder="<?php p($l->t('Redirection URI')); ?>">
<input type="hidden" name="requesttoken" value="<?php p($_['requesttoken']) ?>" />
<input type="submit" class="button" value="<?php p($l->t('Add')); ?>">
</form>
</div>
<div id="oauth2"></div>