| 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__ |