File Coverage

blib/lib/Mojolicious/Plugin/OAuth2/Mock.pm
Criterion Covered Total %
statement 57 59 96.6
branch 9 14 64.2
condition 12 21 57.1
subroutine 13 14 92.8
pod 1 1 100.0
total 92 109 84.4


line stmt bran cond sub pod time code
1             package Mojolicious::Plugin::OAuth2::Mock;
2 2     2   11 use Mojo::Base -base;
  2         4  
  2         14  
3              
4             require Mojolicious::Plugin::OAuth2;
5              
6 2   50 2   402 use constant DEBUG => $ENV{MOJO_OAUTH2_DEBUG} || 0;
  2         3  
  2         2505  
7              
8             has provider => sub {
9             return {
10             authorization_endpoint_url => '/mocked/oauth2/authorize',
11             end_session_endpoint_url => '/mocked/oauth2/logout',
12             issuer_url => '/mocked/oauth2/v2.0',
13             jwks_url => '/mocked/oauth2/keys',
14             return_code => 'fake_code',
15             return_token => 'fake_token',
16             token_endpoint_url => '/mocked/oauth2/token',
17             };
18             };
19              
20             has _rsa => sub { require Crypt::OpenSSL::RSA; Crypt::OpenSSL::RSA->generate_key(2048) };
21              
22             sub apply_to {
23 2 50   2 1 11 my $self = ref $_[0] ? shift : shift->SUPER::new;
24 2         10 my ($app, $provider) = @_;
25              
26 2 50       10 map { $self->provider->{$_} = $provider->{$_} } keys %$provider if $provider;
  9         29  
27 2         9 push @{$app->renderer->classes}, __PACKAGE__;
  2         7  
28              
29             # Add mocked routes for "authorize", "token", ...
30 2         17 for my $k (keys %{$self->provider}) {
  2         5  
31 23 100       4028 next unless $k =~ m!^([a-z].+)_url$!;
32 15         52 my $method = "_action_$1";
33 15         33 my $url = $self->provider->{$k};
34 15         48 warn "[Oauth2::Mock] $url => $method()\n" if DEBUG;
35 15     13   35 $app->routes->any($url => sub { $self->$method(@_) });
  13         105356  
36             }
37             }
38              
39             sub _action_authorization_endpoint {
40 3     3   10 my ($self, $c) = @_;
41              
42 3 100       12 if ($c->param('response_mode') eq 'form_post') {
43 1         565 return $c->render(
44             template => 'oauth2/mock/form_post',
45             format => 'html',
46             code => "authorize-code",
47             redirect_uri => $c->param('redirect_uri'),
48             state => $c->param('state')
49             );
50             }
51              
52             # $c->param('response_mode') eq 'query'
53 2         936 my $url = Mojo::URL->new($c->param('redirect_uri'));
54 2         269 $url->query({code => 'authorize-code', state => $c->param('state')});
55 2         358 return $c->redirect_to($url);
56             }
57              
58             sub _action_authorize {
59 1     1   3 my ($self, $c) = @_;
60              
61 1 50 33     4 if ($c->param('client_id') and $c->param('redirect_uri')) {
62 1         355 my $url = Mojo::URL->new($c->param('redirect_uri'));
63 1         113 $url->query->append(code => $self->provider->{return_code});
64 1     1   40 $c->render(text => $c->tag('a', href => $url, sub {'Connect'}));
  1         40  
65             }
66             else {
67 0         0 $c->render(text => "Invalid request\n", status => 400);
68             }
69             }
70              
71             sub _action_end_session_endpoint {
72 1     1   3 my ($self, $c) = @_;
73 1         4 my $rp_url = Mojo::URL->new($c->param('post_logout_redirect_uri'))
74             ->query({id_token_hint => $c->param('id_token_hint'), state => $c->param('state')});
75 1         556 $c->redirect_to($rp_url);
76             }
77              
78             sub _action_issuer {
79 0     0   0 my ($self, $c) = @_;
80             }
81              
82             sub _action_jwks {
83 1     1   3 my ($self, $c) = @_;
84              
85 1         3 my ($n, $e) = $self->_rsa->get_key_parameters;
86 1         610 my $x5c = $self->_rsa->get_public_key_string;
87 1         30 $x5c =~ s/\n/\\n/g;
88              
89 1         8 require MIME::Base64;
90             return $c->render(
91             template => 'oauth2/mock/keys',
92             format => 'json',
93             n => MIME::Base64::encode_base64url($n->to_bin),
94             e => MIME::Base64::encode_base64url($e->to_bin),
95             x5c => $x5c,
96 1         12 issuer => $c->url_for($self->provider->{issuer_url})->to_abs,
97             );
98             }
99              
100             sub _action_token {
101 1     1   4 my ($self, $c) = @_;
102              
103             return $c->render(text => 'FAIL OVERFLOW', status => 404)
104 1 50       3 unless 3 == grep { $c->param($_) } qw(client_secret redirect_uri code);
  3         499  
105              
106             $c->render(
107             text => Mojo::Parameters->new(
108             access_token => $self->provider->{return_token},
109             expires_in => 3600,
110             refresh_token => Mojo::Util::md5_sum(rand),
111 1   50     50 scope => $self->provider->{scopes} || 'some list of scopes',
112             token_type => 'bearer',
113             )->to_string
114             );
115             }
116              
117             sub _action_token_endpoint {
118 5     5   14 my ($self, $c) = @_;
119 5 50 66     17 return $c->render(json => {error => 'invalid_request'}, status => 500)
      66        
      33        
      66        
120             unless (($c->param('client_secret') and $c->param('redirect_uri') and $c->param('code'))
121             || ($c->param('grant_type') eq 'refresh_token' and $c->param('refresh_token')));
122              
123             my $claims = {
124             aud => $c->param('client_id'),
125             email => 'foo.bar@example.com',
126 5         2717 iss => $c->url_for($self->provider->{issuer_url})->to_abs,
127             name => 'foo bar',
128             preferred_username => 'foo.bar@example.com',
129             sub => 'foo.bar'
130             };
131              
132 5         1880 require Mojo::JWT;
133 5         21 my $id_token = Mojo::JWT->new(
134             algorithm => 'RS256',
135             secret => $self->_rsa->get_private_key_string,
136             set_iat => 1,
137             claims => $claims,
138             header => {kid => 'TEST_SIGNING_KEY'}
139             );
140              
141 5   100     321 return $c->render(
142             template => 'oauth2/mock/token',
143             format => 'json',
144             id_token => $id_token->expires(Mojo::JWT->now + 3600)->encode,
145             refresh_token => $c->param('refresh_token') // 'refresh-token',
146             );
147             }
148              
149             sub _action_well_known {
150 1     1   3 my ($self, $c) = @_;
151 1         5 my $provider = $self->provider;
152 1         6 my $req_url = $c->req->url->to_abs;
153 1     5   177 my $to_abs = sub { $req_url->path(Mojo::URL->new(shift)->path)->to_abs };
  5         457  
154              
155             $c->render(
156             template => 'oauth2/mock/configuration',
157             format => 'json',
158             authorization_endpoint => $to_abs->($provider->{authorization_endpoint_url}),
159             end_session_endpoint => $to_abs->($provider->{end_session_endpoint_url}),
160             issuer => $to_abs->($provider->{issuer_url}),
161             jwks_uri => $to_abs->($provider->{jwks_url}),
162 1         4 token_endpoint => $to_abs->($provider->{token_endpoint_url}),
163             );
164             }
165              
166             1;
167              
168             =encoding utf8
169              
170             =head1 NAME
171              
172             Mojolicious::Plugin::OAuth2::Mock - Mock an Oauth2 and/or OpenID Connect provider
173              
174             =head1 SYNOPSIS
175              
176             use Mojolicious::Plugin::OAuth2::Mock;
177             use Mojolicious;
178              
179             my $app = Mojolicious->new;
180             Mojolicious::Plugin::OAuth2::Mock->apply_to($app);
181              
182             =head1 DESCRIPTION
183              
184             L is an EXPERIMENTAL module to make it
185             easier to test your L based code.
186              
187             =head1 METHODS
188              
189             =head2 apply_to
190              
191             Mojolicious::Plugin::OAuth2::Mock->apply_to($app, \%provider_args);
192             $mock->apply_to($app, \%provider_args);
193              
194             Used to add mocked routes to a L application, based on all the
195             keys in C<%provider_args> that end with "_url". Example:
196              
197              
198             * authorize_url => /mocked/oauth/authorize
199             * authorization_endpoint_url => /mocked/oauth2/authorize
200             * end_session_endpoint_url => /mocked/oauth2/logout
201             * issuer_url => /mocked/oauth2/v2.0
202             * jwks_url => /mocked/oauth2/keys
203             * token_url => /mocked/oauth/token
204             * token_endpoint_url => /mocked/oauth2/token
205              
206             =head1 SEE ALSO
207              
208             L.
209              
210             =cut
211              
212             __DATA__