blib/lib/EveOnline/SSO.pm | |||
---|---|---|---|
Criterion | Covered | Total | % |
statement | 34 | 76 | 44.7 |
branch | 6 | 20 | 30.0 |
condition | 2 | 3 | 66.6 |
subroutine | 12 | 14 | 85.7 |
pod | 3 | 3 | 100.0 |
total | 57 | 116 | 49.1 |
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 | 1335 | use 5.008001; | |||
2 | 6 | ||||||
41 | 2 | 2 | 1037 | use utf8; | |||
2 | 16 | ||||||
2 | 9 | ||||||
42 | 2 | 2 | 852 | use Modern::Perl; | |||
2 | 18251 | ||||||
2 | 13 | ||||||
43 | 2 | 2 | 871 | use JSON::XS; | |||
2 | 3954 | ||||||
2 | 98 | ||||||
44 | 2 | 2 | 931 | use URI::Escape; | |||
2 | 2060 | ||||||
2 | 125 | ||||||
45 | 2 | 2 | 886 | use MIME::Base64; | |||
2 | 978 | ||||||
2 | 94 | ||||||
46 | 2 | 2 | 743 | use URI::URL; | |||
2 | 10327 | ||||||
2 | 88 | ||||||
47 | |||||||
48 | 2 | 2 | 1168 | use LWP::UserAgent; | |||
2 | 69687 | ||||||
2 | 74 | ||||||
49 | 2 | 2 | 1187 | use LWP::Socket; | |||
2 | 19614 | ||||||
2 | 73 | ||||||
50 | |||||||
51 | 2 | 2 | 982 | use Moo; | |||
2 | 17953 | ||||||
2 | 11 | ||||||
52 | |||||||
53 | our $VERSION = "0.01"; | ||||||
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 | 640 | 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 | 49 | ( ( 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 | 12 | my ( $self, %params ) = @_; | ||
164 | |||||||
165 | 2 | 50 | 66 | 15 | return unless $params{code} || $params{refresh_token}; | ||
166 | |||||||
167 | 2 | 50 | 93 | return JSON::XS::decode_json( $self->demo ) if $self->demo; | |||
168 | |||||||
169 | 0 | $self->ua->default_header('Authorization' => "Basic " . encode_base64($self->client_id.':'.$self->client_secret) ); | |||||
170 | 0 | $self->ua->default_header('Content-Type' => "application/x-www-form-urlencoded"); | |||||
171 | |||||||
172 | 0 | my $post_params = {}; | |||||
173 | 0 | foreach my $key ( keys %params ) { | |||||
174 | 0 | $post_params->{$key} = $params{$key}; | |||||
175 | } | ||||||
176 | |||||||
177 | my $res = $self->ua->post($self->token_url, { | ||||||
178 | %$post_params, | ||||||
179 | 0 | 0 | grant_type => $params{code} ? 'authorization_code' : 'refresh_token', | ||||
180 | }); | ||||||
181 | |||||||
182 | 0 | return JSON::XS::decode_json( $res->content ); | |||||
183 | } | ||||||
184 | |||||||
185 | =over | ||||||
186 | |||||||
187 | =item B |
||||||
188 | |||||||
189 | Return hashref with access and refresh tokens by using local webserver for get code. | ||||||
190 | Use callback_url parameter for start private web server on host and port in callback url. | ||||||
191 | |||||||
192 | Default url: http://localhost:10707/ | ||||||
193 | |||||||
194 | # return hash with access and refresh tokens | ||||||
195 | print Dumper $sso->get_token_through_webserver(scope=>'esi-location.read_location.v1'); | ||||||
196 | |||||||
197 | =back | ||||||
198 | =cut | ||||||
199 | |||||||
200 | sub get_token_through_webserver { | ||||||
201 | 0 | 0 | 1 | my ( $self, %params ) = @_; | |||
202 | |||||||
203 | 0 | my $url = $self->get_code( %params ); | |||||
204 | |||||||
205 | 0 | 0 | if ( $url ) { | ||||
206 | |||||||
207 | 0 | say "Go to url: " . $url; | |||||
208 | 0 | my $code = $self->_webserver(); | |||||
209 | |||||||
210 | 0 | 0 | if ( $code ) { | ||||
211 | 0 | say $code; | |||||
212 | 0 | return $self->get_token(code=>$code); | |||||
213 | } | ||||||
214 | } | ||||||
215 | 0 | return; | |||||
216 | } | ||||||
217 | |||||||
218 | sub _webserver { | ||||||
219 | 0 | 0 | my ( $self, %params ) = @_; | ||||
220 | |||||||
221 | 0 | my $headers = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n"; | |||||
222 | |||||||
223 | 0 | my $conn = new URI::URL $self->callback_url; | |||||
224 | |||||||
225 | 0 | my $sock = new LWP::Socket(); | |||||
226 | 0 | 0 | die "Can't bind a socket" unless $sock->bind($conn->host, $conn->port); | ||||
227 | 0 | $sock->listen(1); | |||||
228 | |||||||
229 | 0 | my $code; | |||||
230 | 0 | while ( my $socket = $sock->accept(1) ) { | |||||
231 | 0 | my $content = "EveOnline::SSO code receiver "; |
|||||
232 | 0 | my $request = ''; | |||||
233 | 0 | $socket->read( \$request ); | |||||
234 | 0 | 0 | if ( $request =~ /code=/g ) { | ||||
235 | 0 | 0 | if ( $request =~ /code=([\w-]+)/ ) { | ||||
236 | 0 | $code = $1; | |||||
237 | 0 | $request =~ s/GET \/\?([^ ]*) HTTP.+/$1/s; | |||||
238 | 0 | $request =~ s/&/ /g; |
|||||
239 | 0 | $request =~ s/=/:/g; | |||||
240 | |||||||
241 | 0 | $content .= $request; | |||||
242 | 0 | $content .= " Now you can close this page Fly safe!"; |
|||||
243 | 0 | $socket->write( $headers . $content ); | |||||
244 | 0 | $socket->shutdown(); | |||||
245 | 0 | $socket = undef; | |||||
246 | 0 | last; | |||||
247 | } | ||||||
248 | } | ||||||
249 | } | ||||||
250 | |||||||
251 | 0 | $sock->shutdown(); | |||||
252 | 0 | $sock = undef; | |||||
253 | 0 | return $code; | |||||
254 | } | ||||||
255 | |||||||
256 | 1; | ||||||
257 | __END__ |