Support plugin source on registration
Change-Id: I0ddc6ff0c0499db3a16114d0c0604fb9615d7127
diff --git a/Changes b/Changes
index d7cdf6c..f4cad8f 100755
--- a/Changes
+++ b/Changes
@@ -3,6 +3,7 @@
- Added OAuth client authorization handling. (diewald)
- Mark public clients as slightly more insecure. (diewald)
- Reintroduce email handle support. (fixes #165; diewald)
+ - Support plugin declarations on registration. (diewald)
0.44 2022-02-31
- Fixed autosecrets migration. (diewald)
diff --git a/dev/scss/base/form.scss b/dev/scss/base/form.scss
index 8275f54..18ad3d4 100644
--- a/dev/scss/base/form.scss
+++ b/dev/scss/base/form.scss
@@ -123,6 +123,45 @@
color: $dark-green;
}
+ span.file-upload {
+ @include choose-item;
+ box-shadow: $choose-box-shadow;
+ border: $choose-border;
+ border-radius: $standard-border-radius;
+ padding: $item-padding;
+ position: relative;
+ overflow: hidden;
+ right: 0;
+ display: inline-block;
+
+ &:hover {
+ @include choose-hover;
+ transition: none;
+ }
+
+ > input[type=file] {
+ position: absolute;
+ top: 0;
+ left: 0;
+ margin: 0;
+ padding: 3em;
+ font-size: 20px;
+ cursor: pointer;
+ opacity: 0;
+ filter: alpha(opacity=0);
+ &.field-with-error {
+ background-color: $ids-pink-1;
+ opacity: .3;
+ filter: alpha(opacity=.3);
+ }
+ }
+
+ &::after {
+ @include icon-font;
+ content: $fa-upload;
+ }
+ }
+
a.form-button:hover {
color: inherit !important;
}
@@ -139,7 +178,7 @@
border-color: $darkest-orange !important;
}
-button {
+button, input[type=submit] {
cursor: pointer;
+ button {
@@ -162,8 +201,6 @@
font-family: "FontAwesome";
}
- border: $border-size solid $nearly-white;
-
&:hover,
&:focus {
@include choose-hover;
@@ -192,6 +229,17 @@
}
}
+button[type=submit] {
+ border: $border-size solid $nearly-white;
+}
+
+*[type=submit].form-submit {
+ box-shadow: $choose-box-shadow;
+ border-radius: $standard-border-radius;
+ border-width: 2px !important;
+ padding: $base-padding !important;
+}
+
/**
* Checkbox styling
* http://stackoverflow.com/questions/4148499/how-to-style-checkbox-using-css
diff --git a/dev/scss/base/icons.scss b/dev/scss/base/icons.scss
index 4d4a37c..001b272 100644
--- a/dev/scss/base/icons.scss
+++ b/dev/scss/base/icons.scss
@@ -9,6 +9,7 @@
$fa-minimize: "\f0d8";
$fa-close: "\f00d";
$fa-download: "\f019";
+$fa-upload: "\f093";
$fa-info: "\f05a";
$fa-elipsis: "\f141";
$fa-previous: "\f0d9";
diff --git a/kalamar.dict b/kalamar.dict
index 05db62d..6c51d26 100644
--- a/kalamar.dict
+++ b/kalamar.dict
@@ -36,6 +36,7 @@
pwd => 'Passwort',
email => 'Email',
username => 'Benutzername',
+ upload => 'Hochladen',
with => 'mit',
glimpse => {
desc => 'Zeige nur die ersten Treffer in beliebiger Reihenfolge'
@@ -124,6 +125,7 @@
pwd => 'Password',
email => 'Email',
username => 'Username',
+ upload => 'Upload',
with => 'with',
notAvailInCorpus => 'Not available in the current corpus',
pubOn => 'published on',
diff --git a/lib/Kalamar/Plugin/Auth.pm b/lib/Kalamar/Plugin/Auth.pm
index 4bdb1f3..db28491 100644
--- a/lib/Kalamar/Plugin/Auth.pm
+++ b/lib/Kalamar/Plugin/Auth.pm
@@ -4,6 +4,7 @@
use File::Spec::Functions qw/catdir/;
use Mojo::ByteStream 'b';
use Mojo::Util qw!deprecated b64_encode encode!;
+use Mojo::JSON 'decode_json';
use Encode 'is_utf8';
# This is a plugin to deal with the Kustvakt OAuth server.
@@ -78,6 +79,7 @@
revokeSuccess => 'Der Token wurde erfolgreich widerrufen',
paramError => 'Einige Eingaben sind fehlerhaft',
redirectUri => 'Weiterleitungs-Adresse',
+ pluginSrc => 'Beschreibung des Plugins (*.json-Datei)',
homepage => 'Webseite',
desc => 'Kurzbeschreibung',
revoke => 'Widerrufen',
@@ -94,6 +96,7 @@
-long => 'Möchten sie <span class="client-name"><%= $client_name %></span> wirklich löschen?',
short => 'Löschen'
},
+ oauthHint => 'Die folgende Registrierung (und alle Angaben) für API-Clients folgen der <a href="https://oauth.net/" class="external">OAuth-2.0-Spezifikation</a>.',
loginHint => 'Möglicherweise müssen sie sich zunächst einloggen.',
oauthIssueToken => {
-long => 'Stelle einen neuen Token für <span class="client-name"><%= $client_name %></span> aus',
@@ -109,7 +112,8 @@
short => 'Zugriffsrechte erteilen'
},
createdAt => 'Erstellt am <time datetime="<%= stash("date") %>"><%= stash("date") %></date>.',
- expiresIn => 'Läuft in <%= stash("seconds") %> Sekunden ab.'
+ expiresIn => 'Läuft in <%= stash("seconds") %> Sekunden ab.',
+ fileSizeExceeded => 'Dateigröße überschritten'
},
-en => {
loginPlease => 'Please log in!',
@@ -130,6 +134,7 @@
revokeSuccess => 'Token was revoked successfully',
paramError => 'Some fields are invalid',
redirectUri => 'Redirect URI',
+ pluginSrc => 'Declaration of the plugin (*.json file)',
homepage => 'Homepage',
desc => 'Short description',
revoke => 'Revoke',
@@ -146,6 +151,7 @@
-long => 'Do you really want to unregister <span class="client-name"><%= $client_name %></span>?',
short => 'Unregister'
},
+ oauthHint => 'The following registration of API clients follows the <a href="https://oauth.net/" class="external">OAuth 2.0 specification</a>.',
loginHint => 'Maybe you need to log in first?',
oauthIssueToken => {
-long => 'Issue a new token for <span class="client-name"><%= $client_name %></span>',
@@ -161,7 +167,10 @@
short => 'Grant access'
},
createdAt => 'Created at <time datetime="<%= stash("date") %>"><%= stash("date") %></date>.',
- expiresIn => 'Expires in <%= stash("seconds") %> seconds.'
+ expiresIn => 'Expires in <%= stash("seconds") %> seconds.',
+ fileSizeExceeded => 'File size exceeded',
+ confidentialRequired => 'Plugins need to be confidential',
+ jsonRequired => 'Plugin declarations need to be json files',
}
}
}
@@ -234,7 +243,6 @@
}
);
-
# Log in to the system
my $r = $app->routes;
@@ -850,11 +858,14 @@
};
$v->csrf_protect;
- $v->required('name', 'trim')->size(3, 255);
+ $v->required('name', 'trim', 'not_empty')->size(3, 255);
$v->required('type')->in('PUBLIC', 'CONFIDENTIAL');
- $v->required('desc', 'trim')->size(3, 255);
- $v->optional('url', 'trim')->like(qr/^(http|$)/i);
- $v->optional('redirect_uri', 'trim')->like(qr/^(http|$)/i);
+ $v->required('desc', 'trim', 'not_empty')->size(3, 255);
+ $v->optional('url', 'trim', 'not_empty')->like(qr/^(http|$)/i);
+ $v->optional('redirect_uri', 'trim', 'not_empty')->like(qr/^(http|$)/i);
+ $v->optional('src', 'not_empty');
+
+ $c->stash(template => 'auth/clients');
# Render with error
if ($v->has_error) {
@@ -864,8 +875,62 @@
else {
$c->notify(error => $c->loc('Auth_paramError'));
};
- # return $c->redirect_to('oauth-settings');
- return $c->render(template => 'auth/clients');
+ return $c->render;
+ } elsif ($c->req->is_limit_exceeded) {
+ $c->notify(error => $c->loc('Auth_fileSizeExceeded'));
+ return $c->render;
+ };
+
+ my $type = $v->param('type');
+ my $src = $v->param('src');
+ my $src_json;
+
+ my $json_obj = {
+ name => $v->param('name'),
+ type => $type,
+ description => $v->param('desc'),
+ url => $v->param('url'),
+ redirect_uri => $v->param('redirect_uri')
+ };
+
+ # Check plugin source
+ if ($src) {
+
+ # Plugins need to be confidential
+ if ($type ne 'CONFIDENTIAL') {
+ $c->notify(error => $c->loc('Auth_confidentialRequired'));
+ return $c->render;
+ }
+
+ # Source need to be a file upload
+ elsif (!ref $src || !$src->isa('Mojo::Upload')) {
+ $c->notify(error => $c->loc('Auth_jsonRequired'));
+ return $c->render;
+ };
+
+ # Uploads can't be too large
+ if ($src->size > 1_000_000) {
+ $c->notify(error => $c->loc('Auth_fileSizeExceeded'));
+ return $c->render;
+ };
+
+ # Check upload is not empty
+ if ($src->size > 0 && $src->filename ne '') {
+
+ my $asset = $src->asset;
+
+ # Check for json
+ eval {
+ $src_json = decode_json($asset->slurp);
+ };
+
+ if ($@ || !ref $src_json || ref $src_json ne 'HASH') {
+ $c->notify(error => $c->loc('Auth_jsonRequired'));
+ return $c->render;
+ };
+
+ $json_obj->{source} = $src_json;
+ };
};
# Wait for async result
@@ -873,13 +938,7 @@
# Register on server
state $url = Mojo::URL->new($c->korap->api)->path('oauth2/client/register');
- $c->korap_request('POST', $url => {} => json => {
- name => $v->param('name'),
- type => $v->param('type'),
- description => $v->param('desc'),
- url => $v->param('url'),
- redirect_uri => $v->param('redirect_uri')
- })->then(
+ $c->korap_request('POST', $url => {} => json => $json_obj)->then(
sub {
my $tx = shift;
my $result = $tx->result;
diff --git a/lib/Kalamar/Plugin/Auth/templates/auth/clients.html.ep b/lib/Kalamar/Plugin/Auth/templates/auth/clients.html.ep
index 4016919..e25d63f 100644
--- a/lib/Kalamar/Plugin/Auth/templates/auth/clients.html.ep
+++ b/lib/Kalamar/Plugin/Auth/templates/auth/clients.html.ep
@@ -20,11 +20,13 @@
% };
-%= form_for 'oauth-register', class => 'form-table oauth-register', begin
+%= form_for 'oauth-register', class => 'form-table oauth-register', enctype => 'multipart/form-data', begin
<fieldset>
%= csrf_field
<legend><%= loc('Auth_clientRegister') %></legend>
+ <p><%== loc 'Auth_oauthHint' %></p>
+
<div>
%= label_for name => loc('Auth_clientName'), class => 'field-required', maxlength => 255
%= text_field 'name'
@@ -45,15 +47,23 @@
</div>
<div>
- %= label_for name => loc('Auth_homepage')
+ %= label_for url => loc('Auth_homepage')
%= url_field 'url', placeholder => 'https://...'
</div>
<div>
- %= label_for name => loc('Auth_redirectUri')
+ %= label_for redirect_uri => loc('Auth_redirectUri')
%= url_field 'redirect_uri', placeholder => 'https://...'
</div>
- %= submit_button loc('Auth_clientRegister')
+ <div>
+ %= label_for src => loc('Auth_pluginSrc')
+ <span class="file-upload">
+ <span><%= loc('upload') %></span>
+ %= file_field 'src'
+ </span>
+ </div>
+
+ %= submit_button loc('Auth_clientRegister'), class => 'form-submit'
</fieldset>
% end
diff --git a/lib/Kalamar/Plugin/Auth/templates/auth/grant_scope.html.ep b/lib/Kalamar/Plugin/Auth/templates/auth/grant_scope.html.ep
index ba58500..15b21f9 100644
--- a/lib/Kalamar/Plugin/Auth/templates/auth/grant_scope.html.ep
+++ b/lib/Kalamar/Plugin/Auth/templates/auth/grant_scope.html.ep
@@ -20,6 +20,6 @@
%= hidden_field 'scope' => stash('scope')
% };
- <input type="submit" value="<%= loc 'Auth_oauthGrantScope_short' %>" />
- %= link_to loc('abort') => stash('redirect_uri_server') => {} => (class => 'form-button button-abort')
+ <input type="submit" class="form-submit" value="<%= loc 'Auth_oauthGrantScope_short' %>" />
+ %= link_to loc('abort') => stash('redirect_uri_server') => {} => (class => 'form-button button-abort form-submit')
% end
diff --git a/lib/Kalamar/Plugin/Auth/templates/auth/issue-token.html.ep b/lib/Kalamar/Plugin/Auth/templates/auth/issue-token.html.ep
index d79a7a7..cad593d 100644
--- a/lib/Kalamar/Plugin/Auth/templates/auth/issue-token.html.ep
+++ b/lib/Kalamar/Plugin/Auth/templates/auth/issue-token.html.ep
@@ -9,6 +9,6 @@
%= hidden_field 'client-id' => stash('client_id')
%= hidden_field 'name' => param('name')
%#= hidden_field 'client-secret'
- <input type="submit" value="<%= loc 'Auth_oauthIssueToken_short' %>" />
- %= link_to loc('abort') => url_for('oauth-tokens', client_id => stash('client_id')) => {} => (class => 'form-button button-abort')
+ <input type="submit" class="form-submit" value="<%= loc 'Auth_oauthIssueToken_short' %>" />
+ %= link_to loc('abort') => url_for('oauth-tokens', client_id => stash('client_id')) => {} => (class => 'form-button button-abort form-submit')
% end
diff --git a/lib/Kalamar/Plugin/Auth/templates/auth/revoke-token.html.ep b/lib/Kalamar/Plugin/Auth/templates/auth/revoke-token.html.ep
index d54b6f9..0f57bf4 100644
--- a/lib/Kalamar/Plugin/Auth/templates/auth/revoke-token.html.ep
+++ b/lib/Kalamar/Plugin/Auth/templates/auth/revoke-token.html.ep
@@ -8,6 +8,6 @@
%= csrf_field
%= hidden_field 'name' => param('name')
%= hidden_field 'token' => param('token')
- <input type="submit" value="<%= loc 'Auth_oauthRevokeToken_short' %>" />
- %= link_to loc('abort') => url_for('oauth-tokens', client_id => stash('client_id')) => {} => (class => 'form-button button-abort')
+ <input type="submit" class="form-submit" value="<%= loc 'Auth_oauthRevokeToken_short' %>" />
+ %= link_to loc('abort') => url_for('oauth-tokens', client_id => stash('client_id')) => {} => (class => 'form-button button-abort form-submit')
% end
diff --git a/lib/Kalamar/Plugin/Auth/templates/auth/unregister.html.ep b/lib/Kalamar/Plugin/Auth/templates/auth/unregister.html.ep
index a48f516..eea7db8 100644
--- a/lib/Kalamar/Plugin/Auth/templates/auth/unregister.html.ep
+++ b/lib/Kalamar/Plugin/Auth/templates/auth/unregister.html.ep
@@ -7,6 +7,6 @@
%= form_for 'oauth-unregister-post', class => 'form-table', begin
%= csrf_field
%= hidden_field 'client-name' => param('name')
- <input type="submit" value="<%= loc 'Auth_oauthUnregister_short' %>" />
- %= link_to loc('abort') => 'oauth-settings' => {} => (class => 'form-button button-abort')
+ <input type="submit" class="form-submit" value="<%= loc 'Auth_oauthUnregister_short' %>" />
+ %= link_to loc('abort') => 'oauth-settings' => {} => (class => 'form-button button-abort form-submit')
% end
diff --git a/t/plugin/auth-oauth.t b/t/plugin/auth-oauth.t
index e12c394..478c164 100644
--- a/t/plugin/auth-oauth.t
+++ b/t/plugin/auth-oauth.t
@@ -478,6 +478,12 @@
$t->get_ok('/settings/oauth')
->text_is('form.form-table legend', 'Register new client application')
->attr_is('form.oauth-register','action', '/settings/oauth/register')
+ ->text_is('label[for=name]','Name of the client application')
+ ->text_is('label[for=type]','Type of the client application')
+ ->text_is('label[for=desc]','Short description')
+ ->text_is('label[for=url]','Homepage')
+ ->text_is('label[for=redirect_uri]','Redirect URI')
+ ->text_is('label[for=src]','Declaration of the plugin (*.json file)')
->element_exists('ul.client-list')
->element_exists_not('ul.client-list > li')
->header_is('Cache-Control','max-age=0, no-cache, no-store, must-revalidate')
@@ -499,7 +505,11 @@
name => 'MyApp',
type => 'CONFIDENTIAL',
desc => 'This is my application',
- csrf_token => $csrf
+ csrf_token => $csrf,
+ src => {
+ filename => '',
+ content => ''
+ }
})
->status_is(200)
->element_exists('div.notify-success')
@@ -877,6 +887,52 @@
->header_is('location', 'http://example.com/?error_description=FAIL')
;
+my $json_post = {
+ name => 'Funny',
+ type => 'PUBLIC',
+ desc => 'This is my application',
+ csrf_token => $csrf,
+ src => 'hMMM'
+};
+
+$t->post_ok('/settings/oauth/register' => form => $json_post)
+ ->status_is(200)
+ ->element_exists('div.notify-error')
+ ->text_is('div.notify-error', 'Plugins need to be confidential')
+ ;
+
+$json_post->{type} = 'CONFIDENTIAL';
+
+$t->post_ok('/settings/oauth/register' => form => $json_post)
+ ->status_is(200)
+ ->element_exists('div.notify-error')
+ ->text_is('div.notify-error', 'Plugin declarations need to be json files')
+ ;
+
+$json_post->{src} = {
+ content => 'jjjjjj',
+ filename => 'fun.txt'
+};
+
+$t->post_ok('/settings/oauth/register' => form => $json_post)
+ ->status_is(200)
+ ->element_exists('div.notify-error')
+ ->text_is('div.notify-error', 'Plugin declarations need to be json files')
+ ;
+
+$json_post->{src} = {
+ content => '{"name":"example"}',
+ filename => 'fun.txt'
+};
+
+$t->post_ok('/settings/oauth/register' => form => $json_post)
+ ->status_is(200)
+ ->element_exists_not('div.notify-error')
+ ->element_exists('div.notify-success')
+ ->text_is('div.notify-success', 'Registration successful')
+ ;
+
+
done_testing;
__END__
diff --git a/t/server/mock.pl b/t/server/mock.pl
index 7deb12a..8064208 100644
--- a/t/server/mock.pl
+++ b/t/server/mock.pl
@@ -544,6 +544,7 @@
my $desc = $json->{description};
my $type = $json->{type};
my $url = $json->{url};
+ my $src = $json->{source};
my $redirect_uri = $json->{redirect_uri};
my $list = $c->app->defaults('oauth.client_list');
@@ -553,7 +554,8 @@
"client_name" => $name,
"client_description" => $desc,
"client_url" => $url,
- "client_redirect_uri" => $redirect_uri
+ "client_redirect_uri" => $redirect_uri,
+ "client_source" => $src
};
if ($redirect_uri && $redirect_uri =~ /FAIL$/) {