blib/lib/EveOnline/SSO.pm | |||
---|---|---|---|
Criterion | Covered | Total | % |
statement | 34 | 77 | 44.1 |
branch | 6 | 20 | 30.0 |
condition | 2 | 3 | 66.6 |
subroutine | 12 | 14 | 85.7 |
pod | 3 | 3 | 100.0 |
total | 57 | 117 | 48.7 |
line | stmt | bran | cond | sub | pod | time | code |
---|---|---|---|---|---|---|---|
1 | |||||||
2 | =encoding utf-8 | ||||||
3 | |||||||
4 | =head1 NAME | ||||||
5 | |||||||
6 | EveOnline::SSO - Module for Single Sign On in EveOnline API-services. | ||||||
7 | |||||||
8 | =head1 SYNOPSIS | ||||||
9 | |||||||
10 | use EveOnline::SSO; | ||||||
11 | |||||||
12 | my $sso = EveOnline::SSO->new(client_id => '03ed7324fe4f455', client_secret => 'bgHejXdYo0YJf9NnYs'); | ||||||
13 | |||||||
14 | # return url for open in browser | ||||||
15 | print $sso->get_code(); | ||||||
16 | # or | ||||||
17 | print $sso->get_code(state => 'some_ids_or_flags'); | ||||||
18 | # or | ||||||
19 | print $sso->get_code(state => 'some_ids_or_flags', scope=>'esi-calendar.respond_calendar_events.v1 esi-location.read_location.v1'); | ||||||
20 | |||||||
21 | # return hash with access and refresh tokens by auth code | ||||||
22 | print Dumper $sso->get_token(code=>'tCaVozogf45ttk-Fb71DeEFcSYJXnCHjhGy'); | ||||||
23 | # or hash with access and refresh tokens by refresh_token | ||||||
24 | print Dumper $sso->get_token(refresh_token=>'berF1ZVu_bkt2ud1JzuqmjFkpafSkobqdso'); | ||||||
25 | |||||||
26 | # return hash with access and refresh tokens through listening light web-server | ||||||
27 | print Dumper $sso->get_token_through_webserver( | ||||||
28 | scope=>'esi-calendar.respond_calendar_events.v1 esi-location.read_location.v1', | ||||||
29 | state=> 'Awesome' | ||||||
30 | ); | ||||||
31 | |||||||
32 | |||||||
33 | =head1 DESCRIPTION | ||||||
34 | |||||||
35 | EveOnline::SSO is a perl module for get auth in https://eveonline.com through Single Sign-On (OAuth) interface. | ||||||
36 | |||||||
37 | =cut | ||||||
38 | |||||||
39 | package EveOnline::SSO; | ||||||
40 | 2 | 2 | 1847 | use 5.008001; | |||
2 | 11 | ||||||
41 | 2 | 2 | 1228 | use utf8; | |||
2 | 29 | ||||||
2 | 11 | ||||||
42 | 2 | 2 | 980 | use Modern::Perl; | |||
2 | 20327 | ||||||
2 | 15 | ||||||
43 | 2 | 2 | 1118 | use JSON::XS; | |||
2 | 3741 | ||||||
2 | 91 | ||||||
44 | 2 | 2 | 826 | use URI::Escape; | |||
2 | 2512 | ||||||
2 | 95 | ||||||
45 | 2 | 2 | 837 | use MIME::Base64; | |||
2 | 1018 | ||||||
2 | 91 | ||||||
46 | 2 | 2 | 743 | use URI::URL; | |||
2 | 11275 | ||||||
2 | 120 | ||||||
47 | |||||||
48 | 2 | 2 | 1189 | use LWP::UserAgent; | |||
2 | 68907 | ||||||
2 | 70 | ||||||
49 | 2 | 2 | 953 | use LWP::Socket; | |||
2 | 16682 | ||||||
2 | 75 | ||||||
50 | |||||||
51 | 2 | 2 | 1028 | use Moo; | |||
2 | 18140 | ||||||
2 | 12 | ||||||
52 | |||||||
53 | our $VERSION = "0.03"; | ||||||
54 | |||||||
55 | |||||||
56 | has 'ua' => ( | ||||||
57 | is => 'ro', | ||||||
58 | default => sub { | ||||||
59 | my $ua = LWP::UserAgent->new(); | ||||||
60 | $ua->agent( 'EveOnline::SSO Perl Client' ); | ||||||
61 | $ua->timeout( 120 ); | ||||||
62 | return $ua; | ||||||
63 | } | ||||||
64 | ); | ||||||
65 | |||||||
66 | has 'auth_url' => ( | ||||||
67 | is => 'ro', | ||||||
68 | default => 'https://login.eveonline.com/oauth/authorize/', | ||||||
69 | |||||||
70 | ); | ||||||
71 | |||||||
72 | has 'token_url' => ( | ||||||
73 | is => 'ro', | ||||||
74 | default => 'https://login.eveonline.com/oauth/token', | ||||||
75 | ); | ||||||
76 | |||||||
77 | has 'callback_url' => ( | ||||||
78 | is => 'rw', | ||||||
79 | default => 'http://localhost:10707/', | ||||||
80 | ); | ||||||
81 | |||||||
82 | has 'client_id' => ( | ||||||
83 | is => 'rw', | ||||||
84 | required => 1, | ||||||
85 | ); | ||||||
86 | |||||||
87 | has 'client_secret' => ( | ||||||
88 | is => 'rw', | ||||||
89 | required => 1, | ||||||
90 | ); | ||||||
91 | |||||||
92 | has 'demo' => ( | ||||||
93 | is => 'rw' | ||||||
94 | ); | ||||||
95 | |||||||
96 | =head1 CONSTRUCTOR | ||||||
97 | |||||||
98 | =over | ||||||
99 | |||||||
100 | =item B |
||||||
101 | |||||||
102 | Require two arguments: client_id and client_secret. | ||||||
103 | Optional arguments: callback_url. Default is http://localhost:10707/ | ||||||
104 | |||||||
105 | Get your client_id and client_secret on EveOnline developers page: | ||||||
106 | L |
||||||
107 | |||||||
108 | =back | ||||||
109 | |||||||
110 | =head1 METHODS | ||||||
111 | |||||||
112 | =over | ||||||
113 | |||||||
114 | =item B |
||||||
115 | |||||||
116 | Return URL for open in browser. | ||||||
117 | |||||||
118 | Optional params: state, scope | ||||||
119 | |||||||
120 | See available scopes on L |
||||||
121 | |||||||
122 | # return url for open in browser | ||||||
123 | print $sso->get_code(); | ||||||
124 | |||||||
125 | # or | ||||||
126 | print $sso->get_code(state => 'some_ids_or_flags'); | ||||||
127 | |||||||
128 | # or | ||||||
129 | print $sso->get_code(scope=>'esi-calendar.respond_calendar_events.v1 esi-location.read_location.v1'); | ||||||
130 | |||||||
131 | =back | ||||||
132 | =cut | ||||||
133 | |||||||
134 | sub get_code { | ||||||
135 | 4 | 4 | 1 | 477 | my ( $self, %params ) = @_; | ||
136 | |||||||
137 | return $self->auth_url . | ||||||
138 | "?response_type=code&client_id=".$self->client_id . | ||||||
139 | "&redirect_uri=".uri_escape( $self->callback_url ) . | ||||||
140 | ( ( defined $params{scope} ) ? "&scope=" . uri_escape( $params{scope} ) : '' ) . | ||||||
141 | 4 | 100 | 30 | ( ( defined $params{state} ) ? "&state=" . uri_escape( $params{state} ) : '' ); | |||
100 | |||||||
142 | } | ||||||
143 | |||||||
144 | =over | ||||||
145 | |||||||
146 | =item B |
||||||
147 | |||||||
148 | Return hashref with access and refresh tokens. | ||||||
149 | refresh_token is undef if code was received without scopes. | ||||||
150 | |||||||
151 | Need "code" or "refresh_token" in arguments. | ||||||
152 | |||||||
153 | # return hash with access and refresh tokens by auth code | ||||||
154 | print Dumper $sso->get_token(code=>'tCaVozogf45ttk-Fb71DeEFcSYJXnCHjhGy'); | ||||||
155 | |||||||
156 | # or hash with access and refresh tokens by refresh_token | ||||||
157 | print Dumper $sso->get_token(refresh_token=>'berF1ZVu_bkt2ud1JzuqmjFkpafSkobqdso'); | ||||||
158 | |||||||
159 | =back | ||||||
160 | =cut | ||||||
161 | |||||||
162 | sub get_token { | ||||||
163 | 2 | 2 | 1 | 11 | my ( $self, %params ) = @_; | ||
164 | |||||||
165 | 2 | 50 | 66 | 9 | return unless $params{code} || $params{refresh_token}; | ||
166 | |||||||
167 | 2 | 50 | 55 | return JSON::XS::decode_json( $self->demo ) if $self->demo; | |||
168 | |||||||
169 | 0 | my $base64 = encode_base64($self->client_id.':'.$self->client_secret,""); | |||||
170 | 0 | $self->ua->default_header('Authorization' => "Basic " . $base64 ); | |||||
171 | 0 | $self->ua->default_header('Content-Type' => "application/x-www-form-urlencoded"); | |||||
172 | |||||||
173 | 0 | my $post_params = {}; | |||||
174 | 0 | foreach my $key ( keys %params ) { | |||||
175 | 0 | $post_params->{$key} = $params{$key}; | |||||
176 | } | ||||||
177 | |||||||
178 | my $res = $self->ua->post($self->token_url, { | ||||||
179 | %$post_params, | ||||||
180 | 0 | 0 | grant_type => $params{code} ? 'authorization_code' : 'refresh_token', | ||||
181 | }); | ||||||
182 | |||||||
183 | 0 | return JSON::XS::decode_json( $res->content ); | |||||
184 | } | ||||||
185 | |||||||
186 | =over | ||||||
187 | |||||||
188 | =item B |
||||||
189 | |||||||
190 | Return hashref with access and refresh tokens by using local webserver for get code. | ||||||
191 | Use callback_url parameter for start private web server on host and port in callback url. | ||||||
192 | |||||||
193 | Default url: http://localhost:10707/ | ||||||
194 | |||||||
195 | # return hash with access and refresh tokens | ||||||
196 | print Dumper $sso->get_token_through_webserver(scope=>'esi-location.read_location.v1'); | ||||||
197 | |||||||
198 | =back | ||||||
199 | =cut | ||||||
200 | |||||||
201 | sub get_token_through_webserver { | ||||||
202 | 0 | 0 | 1 | my ( $self, %params ) = @_; | |||
203 | |||||||
204 | 0 | my $url = $self->get_code( %params ); | |||||
205 | |||||||
206 | 0 | 0 | if ( $url ) { | ||||
207 | |||||||
208 | 0 | say "Go to url: " . $url; | |||||
209 | 0 | my $code = $self->_webserver(); | |||||
210 | |||||||
211 | 0 | 0 | if ( $code ) { | ||||
212 | 0 | say $code; | |||||
213 | 0 | return $self->get_token(code=>$code); | |||||
214 | } | ||||||
215 | } | ||||||
216 | 0 | return; | |||||
217 | } | ||||||
218 | |||||||
219 | sub _webserver { | ||||||
220 | 0 | 0 | my ( $self, %params ) = @_; | ||||
221 | |||||||
222 | 0 | my $headers = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n"; | |||||
223 | |||||||
224 | 0 | my $conn = new URI::URL $self->callback_url; | |||||
225 | |||||||
226 | 0 | my $sock = new LWP::Socket(); | |||||
227 | 0 | 0 | die "Can't bind a socket" unless $sock->bind($conn->host, $conn->port); | ||||
228 | 0 | $sock->listen(1); | |||||
229 | |||||||
230 | 0 | my $code; | |||||
231 | 0 | while ( my $socket = $sock->accept(1) ) { | |||||
232 | 0 | my $content = "EveOnline::SSO code receiver "; |
|||||
233 | 0 | my $request = ''; | |||||
234 | 0 | $socket->read( \$request ); | |||||
235 | 0 | 0 | if ( $request =~ /code=/g ) { | ||||
236 | 0 | 0 | if ( $request =~ /code=([\w-]+)/ ) { | ||||
237 | 0 | $code = $1; | |||||
238 | 0 | $request =~ s/GET \/\?([^ ]*) HTTP.+/$1/s; | |||||
239 | 0 | $request =~ s/&/ /g; |
|||||
240 | 0 | $request =~ s/=/:/g; | |||||
241 | |||||||
242 | 0 | $content .= $request; | |||||
243 | 0 | $content .= " Now you can close this page Fly safe!"; |
|||||
244 | 0 | $socket->write( $headers . $content ); | |||||
245 | 0 | $socket->shutdown(); | |||||
246 | 0 | $socket = undef; | |||||
247 | 0 | last; | |||||
248 | } | ||||||
249 | } | ||||||
250 | } | ||||||
251 | |||||||
252 | 0 | $sock->shutdown(); | |||||
253 | 0 | $sock = undef; | |||||
254 | 0 | return $code; | |||||
255 | } | ||||||
256 | |||||||
257 | 1; | ||||||
258 | __END__ |