Remove JWT flow for authorization
Change-Id: Iab1edbff313efd0988ed68afee9f8e747dc8fe5f
diff --git a/Changes b/Changes
index baa36e4..c09b288 100755
--- a/Changes
+++ b/Changes
@@ -8,6 +8,8 @@
- Enable Zooming on mobile clients. (diewald)
- Auth Plugin sets cookie expiration explicitely
to 3 days. (diewald)
+ - Remove JWT Auth flow, which was deprecated since 0.42.
+ (diewald)
0.44 2022-02-31
- Fixed autosecrets migration. (diewald)
diff --git a/lib/Kalamar/Plugin/Auth.pm b/lib/Kalamar/Plugin/Auth.pm
index 7e712fb..2666108 100644
--- a/lib/Kalamar/Plugin/Auth.pm
+++ b/lib/Kalamar/Plugin/Auth.pm
@@ -3,13 +3,11 @@
use File::Basename 'dirname';
use File::Spec::Functions qw/catdir/;
use Mojo::ByteStream 'b';
-use Mojo::Util qw!deprecated b64_encode encode!;
+use Mojo::Util qw!b64_encode encode!;
use Mojo::JSON qw'decode_json encode_json';
use Encode 'is_utf8';
# This is a plugin to deal with the Kustvakt OAuth server.
-# It establishes both the JWT as well as the OAuth password
-# flow for login.
# All tokens are stored in the session. Access tokens are short-lived,
# which limits the effects of misuse.
# Refresh tokens are bound to client id and client secret,
@@ -37,6 +35,11 @@
$param = { %$param, %$config_param };
};
+ if ($param->{jwt}) {
+ $app->log->error('JWT flow is no longer supported');
+ return;
+ };
+
# Load 'notifications' plugin
unless (exists $app->renderer->helpers->{notify}) {
$app->plugin(Notifications => {
@@ -253,1207 +256,761 @@
# Log in to the system
my $r = $app->routes;
- if ($param->{oauth2}) {
-
- my $client_id = $param->{client_id};
- my $client_secret = $param->{client_secret};
+ my $client_id = $param->{client_id};
+ my $client_secret = $param->{client_secret};
- # Sets a requested token and returns
- # an error, if it didn't work
- $app->helper(
- 'auth.set_tokens_p' => sub {
- my ($c, $json) = @_;
- my $promise = Mojo::Promise->new;
+ # Sets a requested token and returns
+ # an error, if it didn't work
+ $app->helper(
+ 'auth.set_tokens_p' => sub {
+ my ($c, $json) = @_;
+ my $promise = Mojo::Promise->new;
- # No json object
- unless ($json) {
- return $promise->reject({
- message => 'Response is no valid JSON object (remote)'
- });
- };
+ # No json object
+ unless ($json) {
+ return $promise->reject({
+ message => 'Response is no valid JSON object (remote)'
+ });
+ };
- # There is an error here
- # Dealing with errors here
- if ($json->{error} && ref $json->{error} ne 'ARRAY') {
- return $promise->reject(
- {
- message => $json->{error} . ($json->{error_description} ? ': ' . $json->{error_description} : '')
- }
- );
- }
-
- # There is an array of errors
- elsif (my $error = $json->{errors} // $json->{error}) {
- if (ref $error eq 'ARRAY') {
- my @errors = ();
- foreach (@{$error}) {
- if ($_->[1]) {
- push @errors, { code => $_->[0], message => $_->[1]}
- }
- }
- return $promise->reject(@errors);
- }
-
- return $promise->reject({message => $error});
- };
-
- # Everything is fine
- my $access_token = $json->{access_token};
- my $token_type = $json->{token_type};
- my $refresh_token = $json->{refresh_token};
- my $expires_in = $json->{"expires_in"} // $EXPECTED_EXPIRATION_IN;
- my $auth = $token_type . ' ' . $access_token;
- # my $scope = $json->{scope};
-
- # Set session info
- $c->session(auth => $auth);
-
- # Expiration of the token minus tolerance
- $c->session(auth_exp => time + $expires_in - 60);
-
- # Set session info for refresh token
- # This can be stored in the session, as it is useless
- # unless the client secret is stolen
- $c->session(auth_r => $refresh_token) if $refresh_token;
-
- # Set stash info
- $c->stash(auth => $auth);
-
- return $promise->resolve;
- }
- );
-
-
- # Refresh tokens and return a promise
- $app->helper(
- 'auth.refresh_p' => sub {
- my $c = shift;
- my $refresh_token = shift;
-
- # Get OAuth access token
- state $r_url = Mojo::URL->new($c->korap->api)->path('oauth2/token');
-
- $c->app->log->debug("Refresh at $r_url");
-
- return $c->kalamar_ua->post_p($r_url, {} => form => {
- grant_type => 'refresh_token',
- client_id => $client_id,
- client_secret => $client_secret,
- refresh_token => $refresh_token
- })->then(
- sub {
- my $tx = shift;
- my $json = $tx->result->json;
-
- # Response is fine
- if ($tx->res->is_success) {
-
- $c->app->log->info("Refresh was successful");
-
- # Set the tokens and return a promise
- return $c->auth->set_tokens_p($json);
- };
-
- # There is a client error - refresh fails
- if ($tx->res->is_client_error && $json) {
-
- $c->stash(auth => undef);
- $c->stash(auth_exp => undef);
- delete $c->session->{user};
- delete $c->session->{auth};
- delete $c->session->{auth_r};
- delete $c->session->{auth_exp};
-
- # Response is 400
- return Mojo::Promise->reject(
- $json->{error_description} // $c->loc('Auth_refreshFail')
- );
- };
-
- if ($tx->res->is_server_error) {
- return Mojo::Promise->reject(
- '600'
- )
- };
-
- $c->notify(error => $c->loc('Auth_responseError'));
- return Mojo::Promise->reject;
- }
- )
- }
- );
-
- # Get a list of registered clients
- $app->helper(
- 'auth.client_list_p' => sub {
- my $c = shift;
-
- # Get list of registered clients
- state $r_url = Mojo::URL->new($c->korap->api)->path('oauth2/client/list');
-
- # Get the list of all clients
- return $c->korap_request(post => $r_url, {} => form => {
- super_client_id => $client_id,
- super_client_secret => $client_secret,
- authorized_only => 'no'
- })->then(
- sub {
- my $tx = shift;
- my $json = $tx->result->json;
-
- # Response is fine
- if ($tx->res->is_success) {
- return Mojo::Promise->resolve($json);
- };
-
- $c->log->error($c->dumper($tx->res->to_string));
-
- # Failure
- $c->notify(error => $c->loc('Auth_responseError'));
- return Mojo::Promise->reject($json // 'No response');
+ # There is an error here
+ # Dealing with errors here
+ if ($json->{error} && ref $json->{error} ne 'ARRAY') {
+ return $promise->reject(
+ {
+ message => $json->{error} . ($json->{error_description} ? ': ' . $json->{error_description} : '')
}
);
}
- );
-
- # Get a list of registered clients
- $app->helper(
- 'auth.token_list_p' => sub {
- my $c = shift;
- my $user_client_id = shift;
-
- # Revoke the token
- state $r_url = Mojo::URL->new($c->korap->api)->path('oauth2/token/list');
-
- my $form = {
- super_client_id => $client_id,
- super_client_secret => $client_secret,
- token_type => 'access_token',
- };
-
- if ($user_client_id) {
- $form->{client_id} = $user_client_id;
- };
-
- # Get the list of all clients
- return $c->korap_request(post => $r_url, {} => form => $form)->then(
- sub {
- my $tx = shift;
- my $json = $tx->result->json;
-
- # Response is fine
- if ($tx->res->is_success) {
- return Mojo::Promise->resolve($json);
+ # There is an array of errors
+ elsif (my $error = $json->{errors} // $json->{error}) {
+ if (ref $error eq 'ARRAY') {
+ my @errors = ();
+ foreach (@{$error}) {
+ if ($_->[1]) {
+ push @errors, { code => $_->[0], message => $_->[1]}
};
-
- $c->log->error($c->dumper($tx->res->to_string));
-
- # Failure
- $c->notify(error => $c->loc('Auth_responseError'));
- return Mojo::Promise->reject($json // 'No response');
- }
- );
- }
- );
-
-
- # Issue a korap request with "oauth"orization
- # This will override the core request helper
- $app->helper(
- korap_request => sub {
- my $c = shift;
- my $method = shift;
- my $path = shift;
- my @param = @_;
-
- # TODO:
- # Check if $tx is not leaked!
-
- # Get plugin user agent
- my $ua = $c->kalamar_ua;
-
- my $url = Mojo::URL->new($path);
- my $tx = $ua->build_tx(uc($method), $url->clone, @param);
-
- # Set X-Forwarded for
- $tx->req->headers->header(
- 'X-Forwarded-For' => $c->client_ip
- );
-
- # Emit Hook to alter request
- $c->app->plugins->emit_hook(
- before_korap_request => ($c, $tx)
- );
-
- my $h = $tx->req->headers;
-
- # If the request already has an Authorization
- # header, respect it!
- if ($h->authorization) {
- return $ua->start_p($tx);
+ };
+ return $promise->reject(@errors);
};
- # Get auth token
- if (my $auth_token = $c->auth->token) {
+ return $promise->reject({message => $error});
+ };
- # The token is already expired!
- my $exp = $c->session('auth_exp');
- if (defined $exp && $exp < time) {
+ # Everything is fine
+ my $access_token = $json->{access_token};
+ my $token_type = $json->{token_type};
+ my $refresh_token = $json->{refresh_token};
+ my $expires_in = $json->{"expires_in"} // $EXPECTED_EXPIRATION_IN;
+ my $auth = $token_type . ' ' . $access_token;
+ # my $scope = $json->{scope};
- # Remove auth ...
+ # Set session info
+ $c->session(auth => $auth);
+
+ # Expiration of the token minus tolerance
+ $c->session(auth_exp => time + $expires_in - 60);
+
+ # Set session info for refresh token
+ # This can be stored in the session, as it is useless
+ # unless the client secret is stolen
+ $c->session(auth_r => $refresh_token) if $refresh_token;
+
+ # Set stash info
+ $c->stash(auth => $auth);
+
+ return $promise->resolve;
+ }
+ );
+
+
+ # Refresh tokens and return a promise
+ $app->helper(
+ 'auth.refresh_p' => sub {
+ my $c = shift;
+ my $refresh_token = shift;
+
+ # Get OAuth access token
+ state $r_url = Mojo::URL->new($c->korap->api)->path('oauth2/token');
+
+ $c->app->log->debug("Refresh at $r_url");
+
+ return $c->kalamar_ua->post_p($r_url, {} => form => {
+ grant_type => 'refresh_token',
+ client_id => $client_id,
+ client_secret => $client_secret,
+ refresh_token => $refresh_token
+ })->then(
+ sub {
+ my $tx = shift;
+ my $json = $tx->result->json;
+
+ # Response is fine
+ if ($tx->res->is_success) {
+
+ $c->app->log->info("Refresh was successful");
+
+ # Set the tokens and return a promise
+ return $c->auth->set_tokens_p($json);
+ };
+
+ # There is a client error - refresh fails
+ if ($tx->res->is_client_error && $json) {
+
$c->stash(auth => undef);
+ $c->stash(auth_exp => undef);
+ delete $c->session->{user};
+ delete $c->session->{auth};
+ delete $c->session->{auth_r};
+ delete $c->session->{auth_exp};
- # And get refresh token from session
- if (my $refresh_token = $c->session('auth_r')) {
+ # Response is 400
+ return Mojo::Promise->reject(
+ $json->{error_description} // $c->loc('Auth_refreshFail')
+ );
+ };
- $c->app->log->debug("Refresh is required");
+ if ($tx->res->is_server_error) {
+ return Mojo::Promise->reject(
+ '600'
+ )
+ };
- # Refresh
- return $c->auth->refresh_p($refresh_token)->then(
- sub {
- $c->app->log->debug("Search with refreshed tokens");
+ $c->notify(error => $c->loc('Auth_responseError'));
+ return Mojo::Promise->reject;
+ }
+ )
+ }
+ );
- # Tokens were set - now send the request the first time!
- $tx->req->headers->authorization($c->stash('auth'));
- return $ua->start_p($tx);
- }
- );
- }
+ # Get a list of registered clients
+ $app->helper(
+ 'auth.client_list_p' => sub {
+ my $c = shift;
- # The token is expired and no refresh token is
- # available - issue an unauthorized request!
- else {
+ # Get list of registered clients
+ state $r_url = Mojo::URL->new($c->korap->api)->path('oauth2/client/list');
- $c->stash(auth => undef);
- $c->stash(auth_exp => undef);
- delete $c->session->{user};
- delete $c->session->{auth};
- delete $c->session->{auth_r};
- delete $c->session->{auth_exp};
+ # Get the list of all clients
+ return $c->korap_request(post => $r_url, {} => form => {
+ super_client_id => $client_id,
+ super_client_secret => $client_secret,
+ authorized_only => 'no'
+ })->then(
+ sub {
+ my $tx = shift;
+ my $json = $tx->result->json;
- # Warn on Error!
- $c->notify(warn => $c->loc('Auth_tokenExpired'));
- return $ua->start_p($tx);
- };
+ # Response is fine
+ if ($tx->res->is_success) {
+ return Mojo::Promise->resolve($json);
+ };
+
+ $c->log->error($c->dumper($tx->res->to_string));
+
+ # Failure
+ $c->notify(error => $c->loc('Auth_responseError'));
+ return Mojo::Promise->reject($json // 'No response');
+ }
+ );
+ }
+ );
+
+
+ # Get a list of registered clients
+ $app->helper(
+ 'auth.token_list_p' => sub {
+ my $c = shift;
+ my $user_client_id = shift;
+
+ # Revoke the token
+ state $r_url = Mojo::URL->new($c->korap->api)->path('oauth2/token/list');
+
+ my $form = {
+ super_client_id => $client_id,
+ super_client_secret => $client_secret,
+ token_type => 'access_token',
+ };
+
+ if ($user_client_id) {
+ $form->{client_id} = $user_client_id;
+ };
+
+ # Get the list of all clients
+ return $c->korap_request(post => $r_url, {} => form => $form)->then(
+ sub {
+ my $tx = shift;
+ my $json = $tx->result->json;
+
+ # Response is fine
+ if ($tx->res->is_success) {
+ return Mojo::Promise->resolve($json);
+ };
+
+ $c->log->error($c->dumper($tx->res->to_string));
+
+ # Failure
+ $c->notify(error => $c->loc('Auth_responseError'));
+ return Mojo::Promise->reject($json // 'No response');
+ }
+ );
+ }
+ );
+
+
+ # Issue a korap request with "oauth"orization
+ # This will override the core request helper
+ $app->helper(
+ korap_request => sub {
+ my $c = shift;
+ my $method = shift;
+ my $path = shift;
+ my @param = @_;
+
+ # TODO:
+ # Check if $tx is not leaked!
+
+ # Get plugin user agent
+ my $ua = $c->kalamar_ua;
+
+ my $url = Mojo::URL->new($path);
+ my $tx = $ua->build_tx(uc($method), $url->clone, @param);
+
+ # Set X-Forwarded for
+ $tx->req->headers->header(
+ 'X-Forwarded-For' => $c->client_ip
+ );
+
+ # Emit Hook to alter request
+ $c->app->plugins->emit_hook(
+ before_korap_request => ($c, $tx)
+ );
+
+ my $h = $tx->req->headers;
+
+ # If the request already has an Authorization
+ # header, respect it!
+ if ($h->authorization) {
+ return $ua->start_p($tx);
+ };
+
+ # Get auth token
+ if (my $auth_token = $c->auth->token) {
+
+ # The token is already expired!
+ my $exp = $c->session('auth_exp');
+ if (defined $exp && $exp < time) {
+
+ # Remove auth ...
+ $c->stash(auth => undef);
+
+ # And get refresh token from session
+ if (my $refresh_token = $c->session('auth_r')) {
+
+ $c->app->log->debug("Refresh is required");
+
+ # Refresh
+ return $c->auth->refresh_p($refresh_token)->then(
+ sub {
+ $c->app->log->debug("Search with refreshed tokens");
+
+ # Tokens were set - now send the request the first time!
+ $tx->req->headers->authorization($c->stash('auth'));
+ return $ua->start_p($tx);
+ }
+ );
}
- # Auth token is fine
+ # The token is expired and no refresh token is
+ # available - issue an unauthorized request!
else {
- # Set auth
- $h->authorization($auth_token);
- }
+ $c->stash(auth => undef);
+ $c->stash(auth_exp => undef);
+ delete $c->session->{user};
+ delete $c->session->{auth};
+ delete $c->session->{auth_r};
+ delete $c->session->{auth_exp};
+
+ # Warn on Error!
+ $c->notify(warn => $c->loc('Auth_tokenExpired'));
+ return $ua->start_p($tx);
+ };
}
- # No token set
+ # Auth token is fine
else {
- # Return unauthorized request
- return $ua->start_p($tx);
- };
+ # Set auth
+ $h->authorization($auth_token);
+ }
+ }
- # Issue an authorized request and automatically
- # refresh the token on expiration!
- return $ua->start_p($tx)->then(
- sub {
- my $tx = shift;
+ # No token set
+ else {
- # Response is fine
- if ($tx->res->is_success) {
- return Mojo::Promise->resolve($tx);
- }
+ # Return unauthorized request
+ return $ua->start_p($tx);
+ };
- # There is a client error - maybe refresh!
- elsif ($tx->res->is_client_error) {
+ # Issue an authorized request and automatically
+ # refresh the token on expiration!
+ return $ua->start_p($tx)->then(
+ sub {
+ my $tx = shift;
- # Check the error
- my $json = $tx->res->json('/errors/0/1');
- if ($json && ($json =~ /expired|invalid/)) {
- $c->stash(auth => undef);
- $c->stash(auth_exp => undef);
- delete $c->session->{user};
- delete $c->session->{auth};
+ # Response is fine
+ if ($tx->res->is_success) {
+ return Mojo::Promise->resolve($tx);
+ }
- # And get refresh token from session
- if (my $refresh_token = $c->session('auth_r')) {
+ # There is a client error - maybe refresh!
+ elsif ($tx->res->is_client_error) {
- # Refresh
- return $c->auth->refresh_p($refresh_token)->then(
- sub {
- $c->app->log->debug("Search with refreshed tokens");
+ # Check the error
+ my $json = $tx->res->json('/errors/0/1');
+ if ($json && ($json =~ /expired|invalid/)) {
+ $c->stash(auth => undef);
+ $c->stash(auth_exp => undef);
+ delete $c->session->{user};
+ delete $c->session->{auth};
- my $tx = $ua->build_tx(uc($method), $url->clone, @param);
+ # And get refresh token from session
+ if (my $refresh_token = $c->session('auth_r')) {
- # Set X-Forwarded for
- $tx->req->headers->header(
- 'X-Forwarded-For' => $c->client_ip
- );
+ # Refresh
+ return $c->auth->refresh_p($refresh_token)->then(
+ sub {
+ $c->app->log->debug("Search with refreshed tokens");
- # Tokens were set - now send the request the first time!
- $tx->req->headers->authorization($c->stash('auth'));
- return $ua->start_p($tx);
- }
- )
- };
+ my $tx = $ua->build_tx(uc($method), $url->clone, @param);
- # Reject the invalid token
- $c->notify(error => $c->loc('Auth_tokenInvalid'));
- return Mojo::Promise->reject;
+ # Set X-Forwarded for
+ $tx->req->headers->header(
+ 'X-Forwarded-For' => $c->client_ip
+ );
+
+ # Tokens were set - now send the request the first time!
+ $tx->req->headers->authorization($c->stash('auth'));
+ return $ua->start_p($tx);
+ }
+ )
};
- return Mojo::Promise->resolve($tx);
- }
-
- # There is a server error - just report
- elsif ($tx->res->is_server_error) {
- my $err = $tx->res->error;
- if ($err) {
- return Mojo::Promise->reject($err->{code} . ': ' . $err->{message});
- }
- else {
- $c->notify(error => $c->loc('Auth_serverError'));
- return Mojo::Promise->reject;
- };
+ # Reject the invalid token
+ $c->notify(error => $c->loc('Auth_tokenInvalid'));
+ return Mojo::Promise->reject;
};
- $c->notify(error => $c->loc('Auth_responseError'));
- return Mojo::Promise->reject;
+ return Mojo::Promise->resolve($tx);
}
- );
- }
- );
- # Password flow for OAuth
- $r->post('/user/login')->to(
+ # There is a server error - just report
+ elsif ($tx->res->is_server_error) {
+ my $err = $tx->res->error;
+ if ($err) {
+ return Mojo::Promise->reject($err->{code} . ': ' . $err->{message});
+ }
+ else {
+ $c->notify(error => $c->loc('Auth_serverError'));
+ return Mojo::Promise->reject;
+ };
+ };
+
+ $c->notify(error => $c->loc('Auth_responseError'));
+ return Mojo::Promise->reject;
+ }
+ );
+ }
+ );
+
+ # Password flow for OAuth
+ $r->post('/user/login')->to(
+ cb => sub {
+ my $c = shift;
+
+ # Validate input
+ my $v = $c->validation;
+ $v->required('handle_or_email', 'trim');
+ $v->required('pwd', 'trim');
+ $v->csrf_protect;
+ $v->optional('fwd')->closed_redirect;
+
+ my $user = check_decode($v->param('handle_or_email'));
+ unless ($user) {
+ $c->notify(error => $c->loc('Auth_invalidChar'));
+ $c->param(handle_or_email => '');
+ return $c->relative_redirect_to('index');
+ };
+
+ my $fwd = $v->param('fwd');
+
+ # Set flash for redirect
+ $c->flash(handle_or_email => $user);
+
+ if ($v->has_error || index($user, ':') >= 0) {
+ if ($v->has_error('fwd')) {
+ $c->notify(error => $c->loc('Auth_openRedirectFail'));
+ }
+ elsif ($v->has_error('csrf_token')) {
+ $c->notify(error => $c->loc('Auth_csrfFail'));
+ }
+ else {
+ $c->notify(error => $c->loc('Auth_loginFail'));
+ };
+
+ return $c->relative_redirect_to($fwd // 'index');
+ }
+
+ my $pwd = $v->param('pwd');
+
+ $c->app->log->debug("Login from user $user");
+
+ # <specific>
+
+ # Get OAuth access token
+ my $url = Mojo::URL->new($c->korap->api)->path('oauth2/token');
+
+ # Korap request for login
+ $c->korap_request('post', $url, {}, form => {
+ grant_type => 'password',
+ username => $user,
+ password => $pwd,
+ client_id => $client_id,
+ client_secret => $client_secret
+ })->then(
+ sub {
+ # Set the tokens and return a promise
+ return $c->auth->set_tokens_p(shift->result->json)
+ }
+ )->then(
+ sub {
+ # Set user info
+ $c->session(user => $user);
+ $c->stash(user => $user);
+
+ # Notify on success
+ $c->app->log->debug(qq!Login successful: "$user"!);
+ $c->notify(success => $c->loc('Auth_loginSuccess'));
+ }
+ )->catch(
+ sub {
+
+ # Notify the user on login failure
+ unless (@_) {
+ $c->notify(error => $c->loc('Auth_loginFail'));
+ }
+
+ # There are known errors
+ foreach (@_) {
+ if (ref $_ eq 'HASH') {
+ my $err = ($_->{code} ? $_->{code} . ': ' : '') .
+ $_->{message};
+ $c->notify(error => $err);
+ # Log failure
+ $c->app->log->debug($err);
+ }
+ else {
+ $c->notify(error => $_);
+ $c->app->log->debug($_);
+ };
+ };
+
+ $c->app->log->debug(qq!Login fail: "$user"!);
+ }
+ )->finally(
+ sub {
+ # Redirect to slash
+ return $c->relative_redirect_to($fwd // 'index');
+ }
+ )
+
+ # Start IOLoop
+ ->wait;
+
+ return 1;
+ }
+ )->name('login');
+
+
+ # Log out of the session
+ $r->get('/user/logout')->to(
+ cb => sub {
+ my $c = shift;
+
+ # TODO: csrf-protection!
+
+ my $refresh_token = $c->session('auth_r');
+
+ # Revoke the token
+ state $url = Mojo::URL->new($c->korap->api)->path('oauth2/revoke');
+
+ $c->kalamar_ua->post_p($url => {} => form => {
+ client_id => $client_id,
+ client_secret => $client_secret,
+ token => $refresh_token,
+ token_type => 'refresh_token'
+ })->then(
+ sub {
+ my $tx = shift;
+ my $json = $tx->result->json;
+
+ my $promise;
+
+ # Response is fine
+ if ($tx->res->is_success) {
+ $c->app->log->info("Revocation was successful");
+ $c->notify(success => $c->loc('Auth_logoutSuccess'));
+
+ $c->stash(auth => undef);
+ $c->stash(auth_exp => undef);
+ $c->flash(handle_or_email => delete $c->session->{user});
+ delete $c->session->{auth};
+ delete $c->session->{auth_r};
+ delete $c->session->{auth_exp};
+ return Mojo::Promise->resolve;
+ };
+
+ # Token may be invalid
+ $c->notify('error', $c->loc('Auth_logoutFail'));
+
+ # There is a client error - refresh fails
+ if ($tx->res->is_client_error && $json) {
+
+ return Mojo::Promise->reject(
+ $json->{error_description}
+ );
+ };
+
+ # Resource may not be found (404)
+ return Mojo::Promise->reject
+
+ }
+ )->catch(
+ sub {
+ my $err = shift;
+
+ # Server may be irresponsible
+ $c->notify('error', $c->loc('Auth_logoutFail'));
+ return Mojo::Promise->reject($err);
+ }
+ )->finally(
+ sub {
+ return $c->redirect_to('index');
+ }
+ )->wait;
+ }
+ )->name('logout');
+
+
+ # If "experimental_registration" is set, open
+ # OAuth registration dialogues.
+ if ($param->{experimental_client_registration}) {
+
+ # Add settings
+ $app->navi->add(settings => (
+ $app->loc('Auth_oauthSettings'), 'oauth'
+ ));
+
+ # Route to oauth settings
+ $r->get('/settings/oauth')->to(
cb => sub {
my $c = shift;
- # Validate input
- my $v = $c->validation;
- $v->required('handle_or_email', 'trim');
- $v->required('pwd', 'trim');
- $v->csrf_protect;
- $v->optional('fwd')->closed_redirect;
+ _set_no_cache($c->res->headers);
- my $user = check_decode($v->param('handle_or_email'));
- unless ($user) {
- $c->notify(error => $c->loc('Auth_invalidChar'));
- $c->param(handle_or_email => '');
- return $c->relative_redirect_to('index');
+ unless ($c->auth->token) {
+ return $c->render(
+ template => 'exception',
+ msg => $c->loc('Auth_authenticationFail'),
+ status => 401
+ );
};
- my $fwd = $v->param('fwd');
+ # Wait for async result
+ $c->render_later;
- # Set flash for redirect
- $c->flash(handle_or_email => $user);
-
- if ($v->has_error || index($user, ':') >= 0) {
- if ($v->has_error('fwd')) {
- $c->notify(error => $c->loc('Auth_openRedirectFail'));
+ $c->auth->client_list_p->then(
+ sub {
+ $c->stash('client_list' => shift);
}
- elsif ($v->has_error('csrf_token')) {
+ )->catch(
+ sub {
+ return;
+ }
+ )->finally(
+ sub {
+ return $c->render(template => 'auth/clients')
+ }
+ );
+ }
+ )->name('oauth-settings');
+
+ # Route to oauth client registration
+ $r->post('/settings/oauth/register')->to(
+ cb => sub {
+ my $c = shift;
+
+ _set_no_cache($c->res->headers);
+
+ my $v = $c->validation;
+
+ unless ($c->auth->token) {
+ return $c->render(
+ content => 'Unauthorized',
+ status => 401
+ );
+ };
+
+ $v->csrf_protect;
+ $v->required('name', 'trim', 'not_empty')->size(3, 255);
+ $v->required('type')->in('PUBLIC', 'CONFIDENTIAL');
+ $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) {
+ if ($v->has_error('csrf_token')) {
$c->notify(error => $c->loc('Auth_csrfFail'));
}
else {
- $c->notify(error => $c->loc('Auth_loginFail'));
+ $c->notify(error => $c->loc('Auth_paramError'));
};
+ return $c->render;
+ } elsif ($c->req->is_limit_exceeded) {
+ $c->notify(error => $c->loc('Auth_fileSizeExceeded'));
+ return $c->render;
+ };
- return $c->relative_redirect_to($fwd // 'index');
- }
+ my $type = $v->param('type');
+ my $src = $v->param('src');
+ my $src_json;
- my $pwd = $v->param('pwd');
+ my $json_obj = {
+ name => $v->param('name'),
+ type => $type,
+ description => $v->param('desc'),
+ url => $v->param('url'),
+ redirect_uri => $v->param('redirect_uri')
+ };
- $c->app->log->debug("Login from user $user");
+ # Check plugin source
+ if ($src) {
- # <specific>
-
- # Get OAuth access token
- my $url = Mojo::URL->new($c->korap->api)->path('oauth2/token');
-
- # Korap request for login
- $c->korap_request('post', $url, {}, form => {
- grant_type => 'password',
- username => $user,
- password => $pwd,
- client_id => $client_id,
- client_secret => $client_secret
- })->then(
- sub {
- # Set the tokens and return a promise
- return $c->auth->set_tokens_p(shift->result->json)
- }
- )->then(
- sub {
- # Set user info
- $c->session(user => $user);
- $c->stash(user => $user);
-
- # Notify on success
- $c->app->log->debug(qq!Login successful: "$user"!);
- $c->notify(success => $c->loc('Auth_loginSuccess'));
- }
- )->catch(
- sub {
-
- # Notify the user on login failure
- unless (@_) {
- $c->notify(error => $c->loc('Auth_loginFail'));
- }
-
- # There are known errors
- foreach (@_) {
- if (ref $_ eq 'HASH') {
- my $err = ($_->{code} ? $_->{code} . ': ' : '') .
- $_->{message};
- $c->notify(error => $err);
- # Log failure
- $c->app->log->debug($err);
- }
- else {
- $c->notify(error => $_);
- $c->app->log->debug($_);
- };
- };
-
- $c->app->log->debug(qq!Login fail: "$user"!);
- }
- )->finally(
- sub {
- # Redirect to slash
- return $c->relative_redirect_to($fwd // 'index');
- }
- )
-
- # Start IOLoop
- ->wait;
-
- return 1;
- }
- )->name('login');
-
-
- # Log out of the session
- $r->get('/user/logout')->to(
- cb => sub {
- my $c = shift;
-
- # TODO: csrf-protection!
-
- my $refresh_token = $c->session('auth_r');
-
- # Revoke the token
- state $url = Mojo::URL->new($c->korap->api)->path('oauth2/revoke');
-
- $c->kalamar_ua->post_p($url => {} => form => {
- client_id => $client_id,
- client_secret => $client_secret,
- token => $refresh_token,
- token_type => 'refresh_token'
- })->then(
- sub {
- my $tx = shift;
- my $json = $tx->result->json;
-
- my $promise;
-
- # Response is fine
- if ($tx->res->is_success) {
- $c->app->log->info("Revocation was successful");
- $c->notify(success => $c->loc('Auth_logoutSuccess'));
-
- $c->stash(auth => undef);
- $c->stash(auth_exp => undef);
- $c->flash(handle_or_email => delete $c->session->{user});
- delete $c->session->{auth};
- delete $c->session->{auth_r};
- delete $c->session->{auth_exp};
- return Mojo::Promise->resolve;
- }
-
- # Token may be invalid
- $c->notify('error', $c->loc('Auth_logoutFail'));
-
- # There is a client error - refresh fails
- if ($tx->res->is_client_error && $json) {
-
- return Mojo::Promise->reject(
- $json->{error_description}
- );
- };
-
- # Resource may not be found (404)
- return Mojo::Promise->reject
-
- }
- )->catch(
- sub {
- my $err = shift;
-
- # Server may be irresponsible
- $c->notify('error', $c->loc('Auth_logoutFail'));
- return Mojo::Promise->reject($err);
- }
- )->finally(
- sub {
- return $c->redirect_to('index');
- }
- )->wait;
- }
- )->name('logout');
-
-
- # If "experimental_registration" is set, open
- # OAuth registration dialogues.
- if ($param->{experimental_client_registration}) {
-
- # Add settings
- $app->navi->add(settings => (
- $app->loc('Auth_oauthSettings'), 'oauth'
- ));
-
- # Route to oauth settings
- $r->get('/settings/oauth')->to(
- cb => sub {
- my $c = shift;
-
- _set_no_cache($c->res->headers);
-
- unless ($c->auth->token) {
- return $c->render(
- template => 'exception',
- msg => $c->loc('Auth_authenticationFail'),
- status => 401
- );
- };
-
- # Wait for async result
- $c->render_later;
-
- $c->auth->client_list_p->then(
- sub {
- $c->stash('client_list' => shift);
- }
- )->catch(
- sub {
- return;
- }
- )->finally(
- sub {
- return $c->render(template => 'auth/clients')
- }
- );
- }
- )->name('oauth-settings');
-
- # Route to oauth client registration
- $r->post('/settings/oauth/register')->to(
- cb => sub {
- my $c = shift;
-
- _set_no_cache($c->res->headers);
-
- my $v = $c->validation;
-
- unless ($c->auth->token) {
- return $c->render(
- content => 'Unauthorized',
- status => 401
- );
- };
-
- $v->csrf_protect;
- $v->required('name', 'trim', 'not_empty')->size(3, 255);
- $v->required('type')->in('PUBLIC', 'CONFIDENTIAL');
- $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) {
- if ($v->has_error('csrf_token')) {
- $c->notify(error => $c->loc('Auth_csrfFail'));
- }
- else {
- $c->notify(error => $c->loc('Auth_paramError'));
- };
+ # Plugins need to be confidential
+ if ($type ne 'CONFIDENTIAL') {
+ $c->notify(error => $c->loc('Auth_confidentialRequired'));
return $c->render;
- } elsif ($c->req->is_limit_exceeded) {
+ }
+
+ # 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;
};
- my $type = $v->param('type');
- my $src = $v->param('src');
- my $src_json;
+ # Check upload is not empty
+ if ($src->size > 0 && $src->filename ne '') {
- my $json_obj = {
- name => $v->param('name'),
- type => $type,
- description => $v->param('desc'),
- url => $v->param('url'),
- redirect_uri => $v->param('redirect_uri')
- };
+ my $asset = $src->asset;
- # Check plugin source
- if ($src) {
+ # Check for json
+ eval {
+ $src_json = decode_json($asset->slurp);
+ };
- # 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')) {
+ if ($@ || !ref $src_json || ref $src_json ne 'HASH') {
$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;
- };
+ $json_obj->{source} = $src_json;
};
-
- # Wait for async result
- $c->render_later;
-
- # Register on server
- state $url = Mojo::URL->new($c->korap->api)->path('oauth2/client/register');
- $c->korap_request('POST', $url => {} => json => $json_obj)->then(
- sub {
- my $tx = shift;
- 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;
- };
-
- my $json = $result->json;
-
- my $client_id = $json->{client_id};
- my $client_secret = $json->{client_secret};
-
- $c->stash('client_name' => $v->param('name'));
- $c->stash('client_desc' => $v->param('desc'));
- $c->stash('client_type' => $v->param('type'));
- $c->stash('client_url' => $v->param('url'));
- $c->stash('client_src' => $v->param('source'));
- $c->stash('client_redirect_uri' => $v->param('redirect_uri'));
- $c->stash('client_id' => $client_id);
-
- if ($client_secret) {
- $c->stash('client_secret' => $client_secret);
- };
-
- $c->notify(success => $c->loc('Auth_en_registerSuccess'));
-
- return $c->render(template => 'auth/client');
- }
- )->catch(
- sub {
- $c->notify('error' => $c->loc('Auth_en_registerFail'));
- }
- )->finally(
- sub {
- return $c->redirect_to('settings' => { scope => 'oauth' });
- }
- );
- }
- )->name('oauth-register');
-
-
- # Unregister client page
- $r->get('/settings/oauth/:client_id/unregister')->to(
- cb => sub {
- my $c = shift;
- _set_no_cache($c->res->headers);
- $c->render(template => 'auth/unregister');
- }
- )->name('oauth-unregister');
-
-
- # Unregister client
- $r->post('/settings/oauth/:client_id/unregister')->to(
- cb => sub {
- my $c = shift;
- _set_no_cache($c->res->headers);
-
- my $v = $c->validation;
-
- unless ($c->auth->token) {
- return $c->render(
- content => 'Unauthorized',
- status => 401
- );
- };
-
- $v->csrf_protect;
- $v->required('client-name', 'trim')->size(3, 255);
-
- # Render with error
- if ($v->has_error) {
- if ($v->has_error('csrf_token')) {
- $c->notify(error => $c->loc('Auth_csrfFail'));
- }
- else {
- $c->notify(error => $c->loc('Auth_paramError'));
- };
- return $c->redirect_to('oauth-settings');
- };
-
- my $client_id = $c->stash('client_id');
- my $client_name = $v->param('client-name');
- my $client_secret = $v->param('client-secret');
-
- # Get list of registered clients
- my $r_url = Mojo::URL->new($c->korap->api)->path('oauth2/client/deregister/')->path(
- $client_id
- );
-
- my $send = {};
-
- if ($client_secret) {
- $send->{client_secret} = $client_secret;
- };
-
- # Get the list of all clients
- return $c->korap_request(delete => $r_url, {} => form => $send)->then(
- sub {
- my $tx = shift;
-
- # Response is fine
- if ($tx->res->is_success) {
- # Okay
- $c->notify(success => 'Successfully deleted ' . $client_name);
- }
- else {
-
- # Failure
- my $json = $tx->result->json;
- if ($json && $json->{error_description}) {
- $c->notify(error => $json->{error_description});
- } else {
- $c->notify(error => $c->loc('Auth_responseError'));
- };
- };
-
- return $c->redirect_to('oauth-settings');
- }
- );
- }
- )->name('oauth-unregister-post');
-
-
- # OAuth Client authorization
- $r->get('/settings/oauth/authorize')->to(
- cb => sub {
- my $c = shift;
-
- _set_no_cache($c->res->headers);
-
- my $v = $c->validation;
- $v->required('client_id');
- $v->optional('scope');
- $v->optional('state');
- $v->optional('redirect_uri');
-
- # Redirect with error
- if ($v->has_error) {
- $c->notify(error => $c->loc('Auth_paramError'));
- return $c->redirect_to;
- };
-
- foreach (qw!scope client_id state redirect_uri!) {
- $c->stash($_, $v->param($_));
- };
-
- # Wait for async result
- $c->render_later;
-
- my $client_id = $v->param('client_id');
-
- my $client_information = $c->auth->client_list_p->then(
- sub {
- my $clients = shift;
- foreach (@$clients) {
- if ($_->{client_id} eq $client_id) {
- $c->stash(client_name => $_->{'client_name'});
- $c->stash(client_type => $_->{'client_type'});
- $c->stash(client_desc => $_->{'client_description'});
- $c->stash(client_url => $_->{'client_url'});
- $c->stash(redirect_uri_server => $_->{'client_redirect_uri'});
- last;
- };
- };
- }
- )->catch(
- sub {
- $c->stash(client_type => 'PUBLIC');
- $c->stash(client_name => $v->param('client_id'));
- return;
- }
- )->finally(
- sub {
-
- # Get auth token
- my $auth_token = $c->auth->token;
-
- # User is not logged in - log in before!
- unless ($auth_token) {
- return $c->render(template => 'auth/login');
- };
-
- # Grant authorization
- return $c->render(template => 'auth/grant_scope');
- }
- );
- }
- )->name('oauth-grant-scope');
-
-
- # OAuth Client authorization
- # This will return a location information including some info
- $r->post('/settings/oauth/authorize')->to(
- cb => sub {
- my $c = shift;
-
- _set_no_cache($c->res->headers);
-
- # It's necessary that it's clear this was triggered by
- # KorAP and not by the client!
- my $v = $c->validation;
- $v->csrf_protect;
- $v->required('client_id');
- $v->optional('scope');
- $v->optional('state');
- $v->optional('redirect_uri');
-
- # WARN! SIGN THIS TO PREVENT OPEN REDIRECT ATTACKS!
- $v->required('redirect_uri_server');
-
- # Render with error
- if ($v->has_error) {
- my $url = Mojo::URL->new($v->param('redirect_uri_server') // $c->url_for('index'));
-
- if ($v->has_error('csrf_token')) {
- $url->query([error_description => $c->loc('Auth_csrfFail')]);
- }
- else {
- $url->query([error_description => $c->loc('Auth_paramError')]);
- };
-
- return $c->redirect_to($url);
- };
-
- state $r_url = Mojo::URL->new($c->korap->api)->path('oauth2/authorize');
- $c->stash(redirect_uri_server => Mojo::URL->new($v->param('redirect_uri_server')));
-
- return $c->korap_request(post => $r_url, {} => form => {
- response_type => 'code',
- client_id => $v->param('client_id'),
- redirect_uri => $v->param('redirect_uri'),
- state => $v->param('state'),
- scope => $v->param('scope'),
- })->then(
- sub {
- my $tx = shift;
-
- # Check for location header with code in redirects
- my $loc;
- foreach (@{$tx->redirects}) {
- $loc = $_->res->headers->header('Location');
-
- my $url = Mojo::URL->new($loc);
-
- if ($url->query->param('code')) {
- last;
- } elsif (my $err = $url->query->param('error_description')) {
- return Mojo::Promise->reject($err);
- }
- };
-
- return Mojo::Promise->resolve($loc) if $loc;
-
- # Failed redirect, but location set
- if ($tx->res->headers->location) {
- my $url = Mojo::URL->new($tx->res->headers->location);
- if (my $err = $url->query->param('error_description')) {
- return Mojo::Promise->reject($err);
- };
- };
-
- # No location code
- return Mojo::Promise->reject('no location response');
- }
- )->catch(
- sub {
- my $err_msg = shift;
- my $url = $c->stash('redirect_uri_server');
- if ($err_msg) {
- $url = $url->query([error_description => $err_msg]);
- };
- return Mojo::Promise->resolve($url);
- }
- )->then(
- sub {
- my $loc = shift;
- return $c->redirect_to($loc);
- }
- )->wait;
- return $c->rendered;
- }
- )->name('oauth-grant-scope-post');
-
-
- # Show information of a client
- $r->get('/settings/oauth/:client_id')->to(
- cb => sub {
- my $c = shift;
-
- _set_no_cache($c->res->headers);
-
- $c->render_later;
-
- $c->auth->client_list_p->then(
- sub {
- my $json = shift;
-
- my ($item) = grep {
- $c->stash('client_id') eq $_->{client_id}
- } @$json;
-
- unless ($item) {
- return Mojo::Promise->reject;
- };
-
- $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 => ($item->{client_type} // 'PUBLIC'));
- $c->stash(client_src => encode_json($item->{source})) if $item->{source};
-
- $c->auth->token_list_p($c->stash('client_id'));
- }
- )->then(
- sub {
- my $json = shift;
-
- $c->stash(tokens => $json);
-
- return Mojo::Promise->resolve;
- }
- )->catch(
- sub {
- return $c->reply->not_found;
- }
- )->finally(
- sub {
- return $c->render(template => 'auth/client')
- }
- );
-
- return;
- }
- )->name('oauth-tokens');
- };
-
-
- # Ask if new token should be issued
- $r->get('/settings/oauth/:client_id/token')->to(
- cb => sub {
- my $c = shift;
- _set_no_cache($c->res->headers);
- $c->render(template => 'auth/issue-token');
- }
- )->name('oauth-issue-token');
-
-
- # Ask if a token should be revoked
- $r->post('/settings/oauth/:client_id/token/revoke')->to(
- cb => sub {
- shift->render(template => 'auth/revoke-token');
- }
- )->name('oauth-revoke-token');
-
-
- # Issue new token
- $r->post('/settings/oauth/:client_id/token')->to(
- cb => sub {
- my $c = shift;
- _set_no_cache($c->res->headers);
-
- my $v = $c->validation;
-
- unless ($c->auth->token) {
- return $c->render(
- content => 'Unauthorized',
- status => 401
- );
};
- $v->csrf_protect;
- $v->optional('client-secret');
- $v->required('name', 'trim');
+ # Wait for async result
+ $c->render_later;
- # Render with error
- if ($v->has_error) {
- if ($v->has_error('csrf_token')) {
- $c->notify(error => $c->loc('Auth_csrfFail'));
- }
- else {
- $c->notify(error => $c->loc('Auth_paramError'));
- };
- return $c->redirect_to('oauth-settings')
- };
-
- # Get authorization token
- state $r_url = Mojo::URL->new($c->korap->api)->path('oauth2/authorize');
- my $client_id = $c->stash('client_id');
- my $name = $v->param('name');
- my $redirect_url = $c->url_for->query({name => $name});
-
- return $c->korap_request(post => $r_url, {} => form => {
- response_type => 'code',
- client_id => $client_id,
- redirect_uri => $redirect_url,
- # TODO: State
- })->then(
+ # Register on server
+ state $url = Mojo::URL->new($c->korap->api)->path('oauth2/client/register');
+ $c->korap_request('POST', $url => {} => json => $json_obj)->then(
sub {
my $tx = shift;
+ my $result = $tx->result;
- # Strip the token from the location header of the fake redirect
- # TODO: Alternatively redirect!
- my ($code, $scope, $loc, $name);
- foreach (@{$tx->redirects}) {
- $loc = $_->res->headers->header('Location');
- if (index($loc, 'code') > 0) {
- my $q = Mojo::URL->new($loc)->query;
- $code = $q->param('code');
- $scope = $q->param('scope');
- $name = $q->param('name');
- last;
+ 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;
};
- # Fine!
- if ($code) {
- return Mojo::Promise->resolve(
- $client_id,
- $redirect_url,
- $code,
- $scope,
- $name
- );
+ my $json = $result->json;
+
+ my $client_id = $json->{client_id};
+ my $client_secret = $json->{client_secret};
+
+ $c->stash('client_name' => $v->param('name'));
+ $c->stash('client_desc' => $v->param('desc'));
+ $c->stash('client_type' => $v->param('type'));
+ $c->stash('client_url' => $v->param('url'));
+ $c->stash('client_src' => $v->param('source'));
+ $c->stash('client_redirect_uri' => $v->param('redirect_uri'));
+ $c->stash('client_id' => $client_id);
+
+ if ($client_secret) {
+ $c->stash('client_secret' => $client_secret);
};
- return Mojo::Promise->reject;
- }
- )->then(
- sub {
- my ($client_id, $redirect_url, $code, $scope, $name) = @_;
- # Get OAuth access token
- state $r_url = Mojo::URL->new($c->korap->api)->path('oauth2/token');
- return $c->kalamar_ua->post_p($r_url, {} => form => {
- client_id => $client_id,
- # NO CLIENT_SECRET YET SUPPORTED
- grant_type => 'authorization_code',
- code => $code,
- redirect_uri => $redirect_url
- })->then(
- sub {
- my $tx = shift;
- my $json = $tx->res->json;
+ $c->notify(success => $c->loc('Auth_en_registerSuccess'));
- if ($tx->res->is_error) {
- $c->notify(error => 'Unable to fetch new token');
- return Mojo::Promise->reject;
- };
-
- $c->notify(success => 'New access token created');
-
- $c->redirect_to('oauth-tokens' => { client_id => $client_id })
- }
- )->catch(
- sub {
- my $err_msg = shift;
-
- # Only raised in case of connection errors
- if ($err_msg) {
- $c->notify(error => { src => 'Backend' } => $err_msg)
- };
-
- $c->render(
- status => 400,
- template => 'failure'
- );
- }
- )
-
- # Start IOLoop
- ->wait;
-
+ return $c->render(template => 'auth/client');
}
)->catch(
sub {
- my $err_msg = shift;
-
- # Only raised in case of connection errors
- if ($err_msg) {
- $c->notify(error => { src => 'Backend' } => $err_msg)
- };
-
- return $c->render(
- status => 400,
- template => 'failure'
- );
+ $c->notify('error' => $c->loc('Auth_en_registerFail'));
}
- )
-
- # Start IOLoop
- ->wait;
-
- return 1;
+ )->finally(
+ sub {
+ return $c->redirect_to('settings' => { scope => 'oauth' });
+ }
+ );
}
- )->name('oauth-issue-token-post');
+ )->name('oauth-register');
- # Revoke token
- $r->delete('/settings/oauth/:client_id/token')->to(
+ # Unregister client page
+ $r->get('/settings/oauth/:client_id/unregister')->to(
cb => sub {
my $c = shift;
+ _set_no_cache($c->res->headers);
+ $c->render(template => 'auth/unregister');
+ }
+ )->name('oauth-unregister');
+
+
+ # Unregister client
+ $r->post('/settings/oauth/:client_id/unregister')->to(
+ cb => sub {
+ my $c = shift;
+ _set_no_cache($c->res->headers);
my $v = $c->validation;
@@ -1465,9 +1022,7 @@
};
$v->csrf_protect;
- $v->required('token', 'trim');
- $v->optional('name', 'trim');
- my $private_client_id = $c->stash('client_id');
+ $v->required('client-name', 'trim')->size(3, 255);
# Render with error
if ($v->has_error) {
@@ -1477,263 +1032,497 @@
else {
$c->notify(error => $c->loc('Auth_paramError'));
};
- return $c->redirect_to('oauth-tokens', client_id => $private_client_id);
+ return $c->redirect_to('oauth-settings');
};
- # Revoke token using super client privileges
- state $r_url = Mojo::URL->new($c->korap->api)->path('oauth2/revoke/super');
+ my $client_id = $c->stash('client_id');
+ my $client_name = $v->param('client-name');
+ my $client_secret = $v->param('client-secret');
- my $token = $v->param('token');
+ # Get list of registered clients
+ my $r_url = Mojo::URL->new($c->korap->api)->path('oauth2/client/deregister/')->path(
+ $client_id
+ );
- return $c->korap_request(post => $r_url, {} => form => {
- super_client_id => $client_id,
- super_client_secret => $client_secret,
- token => $token
- })->then(
+ my $send = {};
+
+ if ($client_secret) {
+ $send->{client_secret} = $client_secret;
+ };
+
+ # Get the list of all clients
+ return $c->korap_request(delete => $r_url, {} => form => $send)->then(
sub {
my $tx = shift;
# Response is fine
if ($tx->res->is_success) {
- $c->notify(success => $c->loc('Auth_revokeSuccess'));
- return Mojo::Promise->resolve;
- };
-
- return Mojo::Promise->reject;
- }
- )->catch(
- sub {
- my $err_msg = shift;
- if ($err_msg) {
- $c->notify(error => { src => 'Backend' } => $err_msg );
+ # Okay
+ $c->notify(success => 'Successfully deleted ' . $client_name);
}
else {
- $c->notify(error => $c->loc('Auth_revokeFail'));
+
+ # Failure
+ my $json = $tx->result->json;
+ if ($json && $json->{error_description}) {
+ $c->notify(error => $json->{error_description});
+ } else {
+ $c->notify(error => $c->loc('Auth_responseError'));
+ };
};
+
+ return $c->redirect_to('oauth-settings');
}
- )->finally(
- sub {
- return $c->redirect_to('oauth-tokens', client_id => $private_client_id);
- }
- )
-
- # Start IOLoop
- ->wait;
+ );
}
- )->name('oauth-revoke-token-delete');
- }
+ )->name('oauth-unregister-post');
- # Use JWT login
- else {
- deprecated 'JWT flow is deprecated in favor of OAuth2 flow';
-
- # Inject authorization to all korap requests
- $app->hook(
- before_korap_request => sub {
- my ($c, $tx) = @_;
- my $h = $tx->req->headers;
-
- # If the request already has an Authorization
- # header, respect it
- unless ($h->authorization) {
-
- # Get valid auth token and set as header
- if (my $auth_token = $c->auth->token) {
- $h->authorization($auth_token);
- };
- };
- }
- );
-
- # Password flow with JWT
- $r->post('/user/login')->to(
+ # OAuth Client authorization
+ $r->get('/settings/oauth/authorize')->to(
cb => sub {
my $c = shift;
- # Validate input
- my $v = $c->validation;
- $v->required('handle_or_email', 'trim');
- $v->required('pwd', 'trim');
- $v->csrf_protect;
- $v->optional('fwd')->closed_redirect;
+ _set_no_cache($c->res->headers);
- my $user = check_decode($v->param('handle_or_email'));
- unless ($user) {
- $c->notify(error => $c->loc('Auth_invalidChar'));
- $c->param(handle_or_email => '');
- return $c->relative_redirect_to('index');
+ my $v = $c->validation;
+ $v->required('client_id');
+ $v->optional('scope');
+ $v->optional('state');
+ $v->optional('redirect_uri');
+
+ # Redirect with error
+ if ($v->has_error) {
+ $c->notify(error => $c->loc('Auth_paramError'));
+ return $c->redirect_to;
};
- my $fwd = $v->param('fwd');
+ foreach (qw!scope client_id state redirect_uri!) {
+ $c->stash($_, $v->param($_));
+ };
- # Set flash for redirect
- $c->flash(handle_or_email => $user);
+ # Wait for async result
+ $c->render_later;
- if ($v->has_error || index($user, ':') >= 0) {
- if ($v->has_error('fwd')) {
- $c->notify(error => $c->loc('Auth_openRedirectFail'));
+ my $client_id = $v->param('client_id');
+
+ my $client_information = $c->auth->client_list_p->then(
+ sub {
+ my $clients = shift;
+ foreach (@$clients) {
+ if ($_->{client_id} eq $client_id) {
+ $c->stash(client_name => $_->{'client_name'});
+ $c->stash(client_type => $_->{'client_type'});
+ $c->stash(client_desc => $_->{'client_description'});
+ $c->stash(client_url => $_->{'client_url'});
+ $c->stash(redirect_uri_server => $_->{'client_redirect_uri'});
+ last;
+ };
+ };
}
- elsif ($v->has_error('csrf_token')) {
- $c->notify(error => $c->loc('Auth_csrfFail'));
+ )->catch(
+ sub {
+ $c->stash(client_type => 'PUBLIC');
+ $c->stash(client_name => $v->param('client_id'));
+ return;
+ }
+ )->finally(
+ sub {
+
+ # Get auth token
+ my $auth_token = $c->auth->token;
+
+ # User is not logged in - log in before!
+ unless ($auth_token) {
+ return $c->render(template => 'auth/login');
+ };
+
+ # Grant authorization
+ return $c->render(template => 'auth/grant_scope');
+ }
+ );
+ }
+ )->name('oauth-grant-scope');
+
+
+ # OAuth Client authorization
+ # This will return a location information including some info
+ $r->post('/settings/oauth/authorize')->to(
+ cb => sub {
+ my $c = shift;
+
+ _set_no_cache($c->res->headers);
+
+ # It's necessary that it's clear this was triggered by
+ # KorAP and not by the client!
+ my $v = $c->validation;
+ $v->csrf_protect;
+ $v->required('client_id');
+ $v->optional('scope');
+ $v->optional('state');
+ $v->optional('redirect_uri');
+
+ # WARN! SIGN THIS TO PREVENT OPEN REDIRECT ATTACKS!
+ $v->required('redirect_uri_server');
+
+ # Render with error
+ if ($v->has_error) {
+ my $url = Mojo::URL->new($v->param('redirect_uri_server') // $c->url_for('index'));
+
+ if ($v->has_error('csrf_token')) {
+ $url->query([error_description => $c->loc('Auth_csrfFail')]);
}
else {
- $c->notify(error => $c->loc('Auth_loginFail'));
+ $url->query([error_description => $c->loc('Auth_paramError')]);
};
- return $c->relative_redirect_to($fwd // 'index');
- }
+ return $c->redirect_to($url);
+ };
- my $pwd = $v->param('pwd');
+ state $r_url = Mojo::URL->new($c->korap->api)->path('oauth2/authorize');
+ $c->stash(redirect_uri_server => Mojo::URL->new($v->param('redirect_uri_server')));
- $c->app->log->debug("Login from user $user");
-
- my $url = Mojo::URL->new($c->korap->api)->path('auth/apiToken');
-
- # Korap request for login
- $c->korap_request('get', $url, {
-
- # Set authorization header
- Authorization => 'Basic ' . b("$user:$pwd")->b64_encode->trim,
-
+ return $c->korap_request(post => $r_url, {} => form => {
+ response_type => 'code',
+ client_id => $v->param('client_id'),
+ redirect_uri => $v->param('redirect_uri'),
+ state => $v->param('state'),
+ scope => $v->param('scope'),
})->then(
sub {
my $tx = shift;
- # Get the java token
- my $jwt = $tx->result->json;
+ # Check for location header with code in redirects
+ my $loc;
+ foreach (@{$tx->redirects}) {
+ $loc = $_->res->headers->header('Location');
- # No java web token
- unless ($jwt) {
- $c->notify(error => 'Response is no valid JWT (remote)');
- return;
- };
+ my $url = Mojo::URL->new($loc);
- # There is an error here
- # Dealing with errors here
- if (my $error = $jwt->{error} // $jwt->{errors}) {
- if (ref $error eq 'ARRAY') {
- foreach (@$error) {
- unless ($_->[1]) {
- $c->notify(error => $c->loc('Auth_loginFail'));
- }
- else {
- $c->notify(error => $_->[0] . ($_->[1] ? ': ' . $_->[1] : ''));
- };
- };
+ if ($url->query->param('code')) {
+ last;
+ } elsif (my $err = $url->query->param('error_description')) {
+ return Mojo::Promise->reject($err);
}
- else {
- $c->notify(error => 'There is an unknown JWT error');
- };
- return;
};
- # TODO: Deal with user return values.
- my $auth = $jwt->{token_type} . ' ' . $jwt->{token};
+ return Mojo::Promise->resolve($loc) if $loc;
- $c->app->log->debug(qq!Login successful: "$user"!);
+ # Failed redirect, but location set
+ if ($tx->res->headers->location) {
+ my $url = Mojo::URL->new($tx->res->headers->location);
+ if (my $err = $url->query->param('error_description')) {
+ return Mojo::Promise->reject($err);
+ };
+ };
- $user = $jwt->{username} ? $jwt->{username} : $user;
-
- # Set session info
- $c->session(user => $user);
- $c->session(auth => $auth);
-
- # Set stash info
- $c->stash(user => $user);
- $c->stash(auth => $auth);
- $c->notify(success => $c->loc('Auth_loginSuccess'));
+ # No location code
+ return Mojo::Promise->reject('no location response');
}
)->catch(
sub {
- my $e = shift;
-
- # Notify the user
- $c->notify(
- error =>
- ($e->{code} ? $e->{code} . ': ' : '') .
- $e->{message} . ' for Login (remote)'
- );
-
- # Log failure
- $c->app->log->debug(
- ($e->{code} ? $e->{code} . ' - ' : '') .
- $e->{message}
- );
-
- $c->app->log->debug(qq!Login fail: "$user"!);
- $c->notify(error => $c->loc('Auth_loginFail'));
+ my $err_msg = shift;
+ my $url = $c->stash('redirect_uri_server');
+ if ($err_msg) {
+ $url = $url->query([error_description => $err_msg]);
+ };
+ return Mojo::Promise->resolve($url);
}
- )->finally(
+ )->then(
sub {
-
- # Redirect to slash
- return $c->relative_redirect_to($fwd // 'index');
+ my $loc = shift;
+ return $c->redirect_to($loc);
}
- )
-
- # Start IOLoop
- ->wait;
-
- return 1;
+ )->wait;
+ return $c->rendered;
}
- )->name('login');
+ )->name('oauth-grant-scope-post');
- # Log out of the session
- $r->get('/user/logout')->to(
+ # Show information of a client
+ $r->get('/settings/oauth/:client_id')->to(
cb => sub {
my $c = shift;
- # TODO: csrf-protection!
+ _set_no_cache($c->res->headers);
- # Log out of the system
- my $url = Mojo::URL->new($c->korap->api)->path('auth/logout');
+ $c->render_later;
- $c->korap_request(
- 'get', $url
+ $c->auth->client_list_p->then(
+ sub {
+ my $json = shift;
+
+ my ($item) = grep {
+ $c->stash('client_id') eq $_->{client_id}
+ } @$json;
+
+ unless ($item) {
+ return Mojo::Promise->reject;
+ };
+
+ $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 => ($item->{client_type} // 'PUBLIC'));
+ $c->stash(client_src => encode_json($item->{source})) if $item->{source};
+
+ $c->auth->token_list_p($c->stash('client_id'));
+ }
)->then(
- # Logged out
sub {
- my $tx = shift;
- # Clear cache
- # ?? Necesseary
- # $c->chi('user')->remove($c->auth->token);
+ my $json = shift;
- # TODO:
- # Revoke refresh token!
- # based on auth token!
- # my $refresh_token = $c->chi('user')->get('refr_' . $c->auth->token);
- # $c->auth->revoke_token($refresh_token)
+ $c->stash(tokens => $json);
- # Expire session
- $c->session(user => undef);
- $c->session(auth => undef);
- $c->notify(success => $c->loc('Auth_logoutSuccess'));
+ return Mojo::Promise->resolve;
}
-
)->catch(
- # Something went wrong
sub {
- # my $err_msg = shift;
- $c->notify('error', $c->loc('Auth_logoutFail'));
+ return $c->reply->not_found;
}
-
)->finally(
- # Redirect
sub {
- return $c->redirect_to('index');
+ return $c->render(template => 'auth/client')
}
- )
+ );
- # Start IOLoop
- ->wait;
-
- return 1;
+ return;
}
- )->name('logout');
+ )->name('oauth-tokens');
};
+
+ # Ask if new token should be issued
+ $r->get('/settings/oauth/:client_id/token')->to(
+ cb => sub {
+ my $c = shift;
+ _set_no_cache($c->res->headers);
+ $c->render(template => 'auth/issue-token');
+ }
+ )->name('oauth-issue-token');
+
+
+ # Ask if a token should be revoked
+ $r->post('/settings/oauth/:client_id/token/revoke')->to(
+ cb => sub {
+ shift->render(template => 'auth/revoke-token');
+ }
+ )->name('oauth-revoke-token');
+
+
+ # Issue new token
+ $r->post('/settings/oauth/:client_id/token')->to(
+ cb => sub {
+ my $c = shift;
+ _set_no_cache($c->res->headers);
+
+ my $v = $c->validation;
+
+ unless ($c->auth->token) {
+ return $c->render(
+ content => 'Unauthorized',
+ status => 401
+ );
+ };
+
+ $v->csrf_protect;
+ $v->optional('client-secret');
+ $v->required('name', 'trim');
+
+ # Render with error
+ if ($v->has_error) {
+ if ($v->has_error('csrf_token')) {
+ $c->notify(error => $c->loc('Auth_csrfFail'));
+ }
+ else {
+ $c->notify(error => $c->loc('Auth_paramError'));
+ };
+ return $c->redirect_to('oauth-settings')
+ };
+
+ # Get authorization token
+ state $r_url = Mojo::URL->new($c->korap->api)->path('oauth2/authorize');
+ my $client_id = $c->stash('client_id');
+ my $name = $v->param('name');
+ my $redirect_url = $c->url_for->query({name => $name});
+
+ return $c->korap_request(post => $r_url, {} => form => {
+ response_type => 'code',
+ client_id => $client_id,
+ redirect_uri => $redirect_url,
+ # TODO: State
+ })->then(
+ sub {
+ my $tx = shift;
+
+ # Strip the token from the location header of the fake redirect
+ # TODO: Alternatively redirect!
+ my ($code, $scope, $loc, $name);
+ foreach (@{$tx->redirects}) {
+ $loc = $_->res->headers->header('Location');
+ if (index($loc, 'code') > 0) {
+ my $q = Mojo::URL->new($loc)->query;
+ $code = $q->param('code');
+ $scope = $q->param('scope');
+ $name = $q->param('name');
+ last;
+ };
+ };
+
+ # Fine!
+ if ($code) {
+ return Mojo::Promise->resolve(
+ $client_id,
+ $redirect_url,
+ $code,
+ $scope,
+ $name
+ );
+ };
+ return Mojo::Promise->reject;
+ }
+ )->then(
+ sub {
+ my ($client_id, $redirect_url, $code, $scope, $name) = @_;
+
+ # Get OAuth access token
+ state $r_url = Mojo::URL->new($c->korap->api)->path('oauth2/token');
+ return $c->kalamar_ua->post_p($r_url, {} => form => {
+ client_id => $client_id,
+ # NO CLIENT_SECRET YET SUPPORTED
+ grant_type => 'authorization_code',
+ code => $code,
+ redirect_uri => $redirect_url
+ })->then(
+ sub {
+ my $tx = shift;
+ my $json = $tx->res->json;
+
+ if ($tx->res->is_error) {
+ $c->notify(error => 'Unable to fetch new token');
+ return Mojo::Promise->reject;
+ };
+
+ $c->notify(success => 'New access token created');
+
+ $c->redirect_to('oauth-tokens' => { client_id => $client_id })
+ }
+ )->catch(
+ sub {
+ my $err_msg = shift;
+
+ # Only raised in case of connection errors
+ if ($err_msg) {
+ $c->notify(error => { src => 'Backend' } => $err_msg)
+ };
+
+ $c->render(
+ status => 400,
+ template => 'failure'
+ );
+ }
+ )
+
+ # Start IOLoop
+ ->wait;
+
+ }
+ )->catch(
+ sub {
+ my $err_msg = shift;
+
+ # Only raised in case of connection errors
+ if ($err_msg) {
+ $c->notify(error => { src => 'Backend' } => $err_msg)
+ };
+
+ return $c->render(
+ status => 400,
+ template => 'failure'
+ );
+ }
+ )
+
+ # Start IOLoop
+ ->wait;
+
+ return 1;
+ }
+ )->name('oauth-issue-token-post');
+
+
+ # Revoke token
+ $r->delete('/settings/oauth/:client_id/token')->to(
+ cb => sub {
+ my $c = shift;
+
+ my $v = $c->validation;
+
+ unless ($c->auth->token) {
+ return $c->render(
+ content => 'Unauthorized',
+ status => 401
+ );
+ };
+
+ $v->csrf_protect;
+ $v->required('token', 'trim');
+ $v->optional('name', 'trim');
+ my $private_client_id = $c->stash('client_id');
+
+ # Render with error
+ if ($v->has_error) {
+ if ($v->has_error('csrf_token')) {
+ $c->notify(error => $c->loc('Auth_csrfFail'));
+ }
+ else {
+ $c->notify(error => $c->loc('Auth_paramError'));
+ };
+ return $c->redirect_to('oauth-tokens', client_id => $private_client_id);
+ };
+
+ # Revoke token using super client privileges
+ state $r_url = Mojo::URL->new($c->korap->api)->path('oauth2/revoke/super');
+
+ my $token = $v->param('token');
+
+ return $c->korap_request(post => $r_url, {} => form => {
+ super_client_id => $client_id,
+ super_client_secret => $client_secret,
+ token => $token
+ })->then(
+ sub {
+ my $tx = shift;
+
+ # Response is fine
+ if ($tx->res->is_success) {
+ $c->notify(success => $c->loc('Auth_revokeSuccess'));
+ return Mojo::Promise->resolve;
+ };
+
+ return Mojo::Promise->reject;
+ }
+ )->catch(
+ sub {
+ my $err_msg = shift;
+ if ($err_msg) {
+ $c->notify(error => { src => 'Backend' } => $err_msg );
+ }
+ else {
+ $c->notify(error => $c->loc('Auth_revokeFail'));
+ };
+ }
+ )->finally(
+ sub {
+ return $c->redirect_to('oauth-tokens', client_id => $private_client_id);
+ }
+ )
+
+ # Start IOLoop
+ ->wait;
+ }
+ )->name('oauth-revoke-token-delete');
+
$app->log->info('Successfully registered Auth plugin');
};
@@ -1796,13 +1585,6 @@
The client secret of Kalamar to be send with every OAuth 2.0
management request.
-=item B<oauth2>
-
-Initially L<Kalamar-Plugin-Auth> was based on JWT. This parameter
-is historically used to switch between oauth2 and jwt. It is expected
-to be deprecated in the future, but for the moment it is required
-to be set to a true value.
-
=item B<experimental_client_registration>
Activates the oauth client registration flow.
@@ -1811,7 +1593,7 @@
=head2 COPYRIGHT AND LICENSE
-Copyright (C) 2015-2020, L<IDS Mannheim|http://www.ids-mannheim.de/>
+Copyright (C) 2015-2022, L<IDS Mannheim|http://www.ids-mannheim.de/>
Author: L<Nils Diewald|http://nils-diewald.de/>
Kalamar is developed as part of the L<KorAP|http://korap.ids-mannheim.de/>
diff --git a/t/plugin/auth.t b/t/plugin/auth.t
deleted file mode 100644
index db19a36..0000000
--- a/t/plugin/auth.t
+++ /dev/null
@@ -1,243 +0,0 @@
-use Mojo::Base -strict;
-use Test::More;
-use Test::Mojo;
-use Mojo::File qw/path/;
-use Data::Dumper;
-
-
-#####################
-# Start Fake server #
-#####################
-my $mount_point = '/realapi/';
-$ENV{KALAMAR_API} = $mount_point;
-
-my $t = Test::Mojo->new('Kalamar' => {
- Kalamar => {
- plugins => ['Auth'],
- },
- 'Kalamar-Auth' => {
- jwt => 1
- }
-});
-
-# Mount fake backend
-# Get the fixture path
-my $fixtures_path = path(Mojo::File->new(__FILE__)->dirname, '..', 'server');
-my $fake_backend = $t->app->plugin(
- Mount => {
- $mount_point =>
- $fixtures_path->child('mock.pl')
- }
-);
-# Configure fake backend
-$fake_backend->pattern->defaults->{app}->log($t->app->log);
-
-my $q = qr!(?:\"|")!;
-
-$t->get_ok('/realapi/v1.0')
- ->status_is(200)
- ->content_is('Fake server available');
-
-$t->get_ok('/?q=Baum')
- ->status_is(200)
- ->text_like('h1 span', qr/KorAP: Find .Baum./i)
- ->text_like('#total-results', qr/\d+$/)
- ->content_like(qr/${q}authorized${q}:null/)
- ->element_exists_not('div.button.top a')
- ->element_exists_not('aside.active')
- ->element_exists_not('aside.off')
- ;
-
-$t->get_ok('/')
- ->status_is(200)
- ->element_exists('form[action=/user/login] input[name=handle_or_email]')
- ->element_exists('aside.active')
- ->element_exists_not('aside.off')
- ;
-
-$t->post_ok('/user/login' => form => { handle_or_email => 'test', pwd => 'fail' })
- ->status_is(302)
- ->header_is('Location' => '/');
-
-$t->get_ok('/')
- ->status_is(200)
- ->element_exists('div.notify-error')
- ->text_is('div.notify-error', 'Bad CSRF token')
- ->element_exists('input[name=handle_or_email][value=test]')
- ->element_exists_not('div.button.top a')
- ;
-
-$t->post_ok('/user/login' => form => { handle_or_email => 'test', pwd => 'pass' })
- ->status_is(302)
- ->header_is('Location' => '/');
-
-my $csrf = $t->get_ok('/')
- ->status_is(200)
- ->element_exists('div.notify-error')
- ->text_is('div.notify-error', 'Bad CSRF token')
- ->element_exists_not('div.button.top a')
- ->tx->res->dom->at('input[name=csrf_token]')->attr('value')
- ;
-
-$t->post_ok('/user/login' => form => {
- handle_or_email => 'test',
- pwd => 'ldaperr',
- csrf_token => $csrf
-})
- ->status_is(302)
- ->content_is('')
- ->header_is('Location' => '/');
-
-$csrf = $t->get_ok('/')
- ->status_is(200)
- ->element_exists('div.notify-error')
- ->text_is('div.notify-error', '2022: LDAP Authentication failed due to unknown user or password!')
- ->element_exists('input[name=handle_or_email][value=test]')
- ->element_exists_not('div.button.top a')
- ->tx->res->dom->at('input[name=csrf_token]')->attr('value')
- ;
-
-$t->post_ok('/user/login' => form => {
- handle_or_email => 'test',
- pwd => 'unknown',
- csrf_token => $csrf
-})
- ->status_is(302)
- ->content_is('')
- ->header_is('Location' => '/');
-
-$csrf = $t->get_ok('/')
- ->status_is(200)
- ->element_exists('div.notify-error')
- ->text_is('div.notify-error', 'Access denied')
- ->element_exists('input[name=handle_or_email][value=test]')
- ->element_exists_not('div.button.top a')
- ->tx->res->dom->at('input[name=csrf_token]')->attr('value')
- ;
-
-$t->post_ok('/user/login' => form => {
- handle_or_email => 'test',
- pwd => 'pass',
- csrf_token => $csrf
-})
- ->status_is(302)
- ->content_is('')
- ->header_is('Location' => '/');
-
-$t->get_ok('/')
- ->status_is(200)
- ->element_exists_not('div.notify-error')
- ->element_exists('div.notify-success')
- ->text_is('div.notify-success', 'Login successful')
- ->element_exists('aside.off')
- ->element_exists_not('aside.active')
- ->element_exists_not('aside.settings')
- ;
-
-# Now the user is logged in and should be able to
-# search with authorization
-$t->get_ok('/?q=Baum')
- ->status_is(200)
- ->text_like('h1 span', qr/KorAP: Find .Baum./i)
- ->text_like('#total-results', qr/\d+$/)
- ->element_exists_not('div.notify-error')
- ->content_like(qr/${q}authorized${q}:${q}test${q}/)
- ->element_exists('div.button.top a')
- ->element_exists('div.button.top a.logout[title~="test"]')
- ;
-
-# Logout
-$t->get_ok('/user/logout')
- ->status_is(302)
- ->header_is('Location' => '/');
-
-$t->get_ok('/')
- ->status_is(200)
- ->element_exists_not('div.notify-error')
- ->element_exists('div.notify-success')
- ->text_is('div.notify-success', 'Logout successful')
- ;
-
-$t->get_ok('/?q=Baum')
- ->status_is(200)
- ->text_like('h1 span', qr/KorAP: Find .Baum./i)
- ->text_like('#total-results', qr/\d+$/)
- ->content_like(qr/${q}authorized${q}:null/)
- ;
-
-# Get redirect
-my $fwd = $t->get_ok('/?q=Baum&ql=poliqarp')
- ->status_is(200)
- ->element_exists_not('div.notify-error')
- ->tx->res->dom->at('input[name=fwd]')->attr('value')
- ;
-
-is($fwd, '/?q=Baum&ql=poliqarp', 'Redirect is valid');
-
-$t->post_ok('/user/login' => form => {
- handle_or_email => 'test',
- pwd => 'pass',
- csrf_token => $csrf,
- fwd => 'http://bad.example.com/test'
-})
- ->status_is(302)
- ->header_is('Location' => '/');
-
-$t->get_ok('/')
- ->status_is(200)
- ->element_exists('div.notify-error')
- ->element_exists_not('div.notify-success')
- ->text_is('div.notify-error', 'Redirect failure')
- ;
-
-$t->post_ok('/user/login' => form => {
- handle_or_email => 'test',
- pwd => 'pass',
- csrf_token => $csrf,
- fwd => $fwd
-})
- ->status_is(302)
- ->header_is('Location' => '/?q=Baum&ql=poliqarp');
-
-
-done_testing;
-__END__
-
-
-# Login mit falschem Nutzernamen:
-# 400 und:
-{"errors":[[2022,"LDAP Authentication failed due to unknown user or password!"]]}
-
-
-
-ok(!$c->user->get('details'), 'User not logged in');
-
-# Login with user credentials
-ok($c->user->login('kustvakt', 'kustvakt2015'), 'Login with demo user');
-is($c->stash('user'), 'kustvakt', 'Kustvakt is logged in');
-like($c->stash('auth'), qr/^api_token /, 'Kustvakt is logged in');
-
-my $details = $c->user->get('details');
-is($details->{email}, 'kustvakt@ids-mannheim.de', 'Email');
-is($details->{firstName}, 'Kustvakt', 'Firstname');
-is($details->{lastName}, 'KorAP', 'Lastname');
-is($details->{country}, 'Germany', 'Country');
-is($details->{address}, 'Mannheim', 'Address');
-is($details->{username}, 'kustvakt', 'Username');
-is($details->{institution}, 'IDS Mannheim', 'Institution');
-
-my $settings = $c->user->get('settings');
-is($settings->{username}, 'kustvakt', 'Username');
-
-# ok($c->user->set(details => { firstName => 'Me' }), 'Set first name');
-#ok($c->user->set(details => {
-# firstName => 'Akron',
-# lastName => 'Fuxfell'
-#}), 'Set first name');
-
-# diag Dumper $c->user->get('info');
-
-ok(1,'Fine');
-
-done_testing;
-__END__