Support confidential clients
Change-Id: I907592587ae296bef592c2f731a0302c6e9e8c8b
diff --git a/Changes b/Changes
index 00aad71..3131657 100755
--- a/Changes
+++ b/Changes
@@ -1,4 +1,7 @@
-0.44 2022-02-24
+0.45 2022-04-06
+ - Added confidential client support to OAuth. (diewald)
+
+0.44 2022-02-31
- Fixed autosecrets migration. (diewald)
- Format page numbers in pagination. (diewald)
- Introduce tei2korapxml command via plugin. (diewald)
diff --git a/dev/scss/main/oauth.scss b/dev/scss/main/oauth.scss
index db8e09d..c1e38c1 100644
--- a/dev/scss/main/oauth.scss
+++ b/dev/scss/main/oauth.scss
@@ -9,14 +9,17 @@
li.client {
list-style-type: none;
+ margin-bottom: 1.5em;
span.client-name::before {
margin-left: -1.5em;
}
- span.client-desc {
- font-size: 70%;
- display: block;
+ p.client-desc, p.client-url {
+ font-weight: normal;
+ font-size: 80%;
+ margin-top: .2em;
+ margin-bottom: .2em;
}
}
diff --git a/lib/Kalamar.pm b/lib/Kalamar.pm
index 97bb173..00f8242 100644
--- a/lib/Kalamar.pm
+++ b/lib/Kalamar.pm
@@ -8,7 +8,7 @@
use List::Util 'none';
# Minor version - may be patched from package.json
-our $VERSION = '0.44';
+our $VERSION = '0.45';
# Supported version of Backend API
our $API_VERSION = '1.0';
diff --git a/lib/Kalamar/Plugin/Auth.pm b/lib/Kalamar/Plugin/Auth.pm
index 011dae1..963300a 100644
--- a/lib/Kalamar/Plugin/Auth.pm
+++ b/lib/Kalamar/Plugin/Auth.pm
@@ -873,6 +873,14 @@
my $result = $tx->result;
if ($result->is_error) {
+ my $json = $result->json;
+ if ($json && $json->{error}) {
+ $c->notify(
+ error => $json->{error} .
+ ($json->{error_description} ? ': ' . $json->{error_description} : '')
+ )
+ };
+
return Mojo::Promise->reject;
};
@@ -898,10 +906,7 @@
}
)->catch(
sub {
- # Server may be irresponsible
- my $err = shift;
$c->notify('error' => $c->loc('Auth_en_registerFail'));
- return Mojo::Promise->reject($err);
}
)->finally(
sub {
@@ -1018,7 +1023,7 @@
$c->stash(client_name => $item->{client_name});
$c->stash(client_desc => $item->{client_description});
$c->stash(client_url => $item->{client_url});
- $c->stash(client_type => 'PUBLIC');
+ $c->stash(client_type => ($item->{client_type} // 'PUBLIC'));
$c->auth->token_list_p($c->stash('client_id'));
}
diff --git a/lib/Kalamar/Plugin/Auth/templates/auth/client.html.ep b/lib/Kalamar/Plugin/Auth/templates/auth/client.html.ep
index 28074ea..de9e3a6 100644
--- a/lib/Kalamar/Plugin/Auth/templates/auth/client.html.ep
+++ b/lib/Kalamar/Plugin/Auth/templates/auth/client.html.ep
@@ -9,19 +9,23 @@
<li class="client">
<span class="client-name"><%= stash 'client_name' %></span>
% if (stash('client_desc')) {
- <span class="client-desc"><%= stash 'client_desc' %></span>
+ <p class="client-desc"><%= stash 'client_desc' %></p>
% };
% if (stash('client_url')) {
- <span class="client-url"><a href="<%= stash('client_url') %>"><%= stash('client_url') %></a></span>
+ <p class="client-url"><a href="<%= stash('client_url') %>"><%= stash('client_url') %></a></p>
+ % };
+
+ % if (stash('client_redirect_uri')) {
+ <p class="client-redirect-uri"><%= loc 'Auth_redirectUri' %>: <tt><%= stash('client_redirect_uri') %></tt></p>
% };
- <p><%= loc 'Auth_clientType' %>: <tt><%= stash 'client_type' %></tt></p>
+ <p class="client-type"><%= loc 'Auth_clientType' %>: <tt><%= stash 'client_type' %></tt></p>
%= label_for 'client_id' => loc('Auth_clientID')
%= text_field 'client_id', stash('client_id'), readonly => 'readonly', class => 'copy-to-clipboard'
- % if (stash('client_type') && stash('client_type') ne 'PUBLIC') {
+ % if (stash('client_type') && stash('client_type') ne 'PUBLIC' && stash('client_secret')) {
<div>
%= label_for 'client_secret' => loc('Auth_clientSecret')
- %= password_field 'client_secret', value => stash('client_secret'), readonly => 'readonly'
+ %= password_field 'client_secret', value => stash('client_secret'), readonly => 'readonly', class => 'show-pwd copy-to-clipboard'
</div>
% };
@@ -29,7 +33,9 @@
<span class="button-group button-panel">
%= link_to loc('Auth_oauthUnregister_short') => url_for('oauth-unregister', client_id => stash('client_id'))->query('name' => stash('client_name')) => {} => ( class => 'client-unregister' )
- %= link_to loc('Auth_oauthIssueToken_short') => url_for('oauth-issue-token', client_id => stash('client_id'))->query('name' => stash('client_name')) => {} => ( class => 'client-issue-token' )
+ % if (stash('client_type') && stash('client_type') eq 'PUBLIC') {
+ %= link_to loc('Auth_oauthIssueToken_short') => url_for('oauth-issue-token', client_id => stash('client_id'))->query('name' => stash('client_name')) => {} => ( class => 'client-issue-token' )
+ % };
</span>
</li>
</ul>
diff --git a/lib/Kalamar/Plugin/Auth/templates/auth/clients.html.ep b/lib/Kalamar/Plugin/Auth/templates/auth/clients.html.ep
index 928e4c7..cee1abd 100644
--- a/lib/Kalamar/Plugin/Auth/templates/auth/clients.html.ep
+++ b/lib/Kalamar/Plugin/Auth/templates/auth/clients.html.ep
@@ -8,10 +8,12 @@
% foreach (@$list) {
<li class="client">
<span class="client-name"><%= link_to $_->{client_name} => url_for('oauth-tokens', client_id => $_->{client_id}) %></span>
- <span class="client-desc"><%= $_->{client_description} %></span>
-% if ($_->{client_url}) {
- <span class="client-url"><a href="<%= $_->{client_url} %>"><%= $_->{client_url} %></a></span>
-% }
+ % if ($_->{client_description}) {
+ <p class="client-desc"><%= $_->{client_description} %></p>
+ % };
+ % if ($_->{client_url}) {
+ <p class="client-url"><a href="<%= $_->{client_url} %>"><%= $_->{client_url} %></a></p>
+ % }
</li>
% };
</ul>
@@ -32,9 +34,9 @@
%= label_for type => loc('Auth_clientType'), class => 'field-required'
<%= radio_button type => 'PUBLIC', checked => 'checked' %>
<label>Public</label>
-%# <br />
-%# <%= radio_button type => 'CONFIDENTIAL' %>
-%# <label>Confidential</label>
+ <br />
+ <%= radio_button type => 'CONFIDENTIAL' %>
+ <label>Confidential</label>
</div>
<div>
@@ -47,10 +49,10 @@
%= url_field 'url', placeholder => 'https://...'
</div>
-%# <div>
-%# %= label_for name => loc('Auth_redirectUri')
-%# %= url_field 'redirect_uri', placeholder => 'https://...'
-%# </div>
+ <div>
+ %= label_for name => loc('Auth_redirectUri')
+ %= url_field 'redirect_uri', placeholder => 'https://...'
+ </div>
%= submit_button loc('Auth_clientRegister')
</fieldset>
diff --git a/package.json b/package.json
index cdba928..1297967 100755
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
"name": "Kalamar",
"description": "Mojolicious-based Frontend for KorAP",
"license": "BSD-2-Clause",
- "version": "0.44.1",
+ "version": "0.45.0",
"pluginVersion": "0.2.2",
"engines": {
"node": ">=6.0.0"
diff --git a/t/plugin/auth-oauth.t b/t/plugin/auth-oauth.t
index 635df8c..e8ac795 100644
--- a/t/plugin/auth-oauth.t
+++ b/t/plugin/auth-oauth.t
@@ -516,18 +516,18 @@
->text_is('.form-table legend', 'Register new client application')
->attr_is('.oauth-register','action', '/settings/oauth/register')
->text_is('ul.client-list > li > span.client-name a', 'MyApp')
- ->text_is('ul.client-list > li > span.client-desc', 'This is my application')
+ ->text_is('ul.client-list > li > p.client-desc', 'This is my application')
->header_is('Cache-Control','max-age=0, no-cache, no-store, must-revalidate')
->header_is('Expires','Thu, 01 Jan 1970 00:00:00 GMT')
->header_is('Pragma','no-cache')
- ->tx->res->dom->at('ul.client-list > li > span.client-url a')
+ ->tx->res->dom->at('ul.client-list > li > p.client-url a')
;
is(defined $anchor ? $anchor->text : '', '');
$t->get_ok('/settings/oauth/fCBbQkA2NDA3MzM1Yw==')
->status_is(200)
->text_is('ul.client-list > li.client > span.client-name', 'MyApp')
- ->text_is('ul.client-list > li.client > span.client-desc', 'This is my application')
+ ->text_is('ul.client-list > li.client > p.client-desc', 'This is my application')
->text_is('a.client-unregister', 'Unregister')
->attr_is('a.client-unregister', 'href', '/settings/oauth/fCBbQkA2NDA3MzM1Yw==/unregister?name=MyApp')
;
@@ -592,6 +592,7 @@
->header_is('Cache-Control','max-age=0, no-cache, no-store, must-revalidate')
->header_is('Expires','Thu, 01 Jan 1970 00:00:00 GMT')
->header_is('Pragma','no-cache')
+ ->element_exists('.client-issue-token')
;
$t->get_ok('/settings/oauth/fCBbQkA2NDA3MzM1Yw==')
@@ -605,6 +606,7 @@
->text_is('ul.token-list label[for=token]', 'Access Token')
->text_is('p[name=created]', 'Created at ')
->text_is('p[name=expires]', 'Expires in 31533851 seconds.')
+ ->element_exists('.client-issue-token')
;
$csrf = $t->get_ok('/settings/oauth/fCBbQkA2NDA3MzM1Yw==/token?name=MyApp2')
@@ -698,11 +700,58 @@
->header_is('Location','/settings/oauth/fCBbQkA2NDA3MzM1Yw==')
;
-
$t->get_ok('/settings/oauth/fCBbQkA2NDA3MzM1Yw==')
->element_exists_not('div.notify-error')
->text_is('div.notify-success', 'Token was revoked successfully')
;
+$t->app->routes->get('/x/redirect-target')->to(
+ cb => sub {
+ my $c = shift;
+ return $c->render(text => 'redirected');
+ }
+);
+
+$csrf = $t->post_ok('/settings/oauth/register' => form => {
+ name => 'MyConfApp',
+ type => 'CONFIDENTIAL',
+ desc => 'This is my application',
+})
+ ->text_is('div.notify-error', 'Bad CSRF token')
+ ->tx->res->dom->at('input[name="csrf_token"]')
+ ->attr('value')
+ ;
+
+$t->post_ok('/settings/oauth/register' => form => {
+ name => 'MyConfApp',
+ type => 'CONFIDENTIAL',
+ desc => 'This is my confidential application',
+ csrf_token => $csrf,
+ redirect_uri => 'http://localhost/redirect-target'
+})
+ ->text_is('div.notify-error', undef)
+ ->text_is('li.client span.client-name', 'MyConfApp')
+ ->text_is('li.client p.client-desc', 'This is my confidential application')
+ ->text_is('li.client .client-redirect-uri tt', 'http://localhost/redirect-target')
+ ->text_is('li.client .client-type tt', 'CONFIDENTIAL')
+ ->element_exists_not('.client-issue-token')
+ ;
+
+$t->post_ok('/settings/oauth/register' => form => {
+ name => 'MyConfApp2',
+ type => 'CONFIDENTIAL',
+ desc => 'This is my second confidential application',
+ csrf_token => $csrf,
+ redirect_uri => 'http://localhost/FAIL'
+})
+ ->status_is(302)
+ ->header_is('location','/settings/oauth/')
+ ;
+
+$t->get_ok('/settings/oauth/')
+ ->text_is('div.notify-error', 'invalid_request: http://localhost/FAIL is invalid.')
+ ;
+
+
done_testing;
__END__
diff --git a/t/server/mock.pl b/t/server/mock.pl
index 59a6766..d7ec2ff 100644
--- a/t/server/mock.pl
+++ b/t/server/mock.pl
@@ -21,6 +21,7 @@
'access_token_3' => 'jvgjbvjgzucgdwuiKHJK',
'refresh_token_2' => "fghijk",
'new_client_id' => 'fCBbQkA2NDA3MzM1Yw==',
+ 'new_client_id_2' => 'hghGHhjhFRz_gJhjrd==',
'new_client_secret' => 'KUMaFxs6R1WGud4HM22w3HbmYKHMnNHIiLJ2ihaWtB4N5JxGzZgyqs5GTLutrORj',
'auth_token_1' => 'mscajfdghnjdfshtkjcuynxahgz5il'
);
@@ -543,7 +544,7 @@
my $desc = $json->{description};
my $type = $json->{type};
my $url = $json->{url};
- my $redirect_url = $json->{redirect_uri};
+ my $redirect_uri = $json->{redirect_uri};
my $list = $c->app->defaults('oauth.client_list');
@@ -551,13 +552,25 @@
"client_id" => $tokens{new_client_id},
"client_name" => $name,
"client_description" => $desc,
- "client_url" => $url
+ "client_url" => $url,
+ "client_redirect_uri" => $redirect_uri
+ };
+
+ if ($redirect_uri && $redirect_uri =~ /FAIL$/) {
+ return $c->render(
+ status => 400,
+ json => {
+ "error_description" => $redirect_uri . " is invalid.",
+ "error" => "invalid_request"
+ }
+ )
};
# Confidential server application
if ($type eq 'CONFIDENTIAL') {
+
return $c->render(json => {
- client_id => $tokens{new_client_id},
+ client_id => $tokens{new_client_id_2},
client_secret => $tokens{new_client_secret}
});
};