File Coverage

blib/lib/Azure/AD/Password.pm
Criterion Covered Total %
statement 15 33 45.4
branch 0 8 0.0
condition n/a
subroutine 5 9 55.5
pod 1 1 100.0
total 21 51 41.1


line stmt bran cond sub pod time code
1             package Azure::AD::Password;
2 1     1   1326 use Moo;
  1         3  
  1         12  
3 1     1   359 use Azure::AD::Errors;
  1         3  
  1         38  
4 1     1   7 use Types::Standard qw/Str Int InstanceOf/;
  1         3  
  1         9  
5 1     1   845 use JSON::MaybeXS;
  1         2  
  1         68  
6 1     1   6 use HTTP::Tiny;
  1         11  
  1         721  
7              
8             our $VERSION = '0.02';
9              
10             has ua_agent => (is => 'ro', isa => Str, default => sub {
11             'Azure::AD::Password ' . $Azure::AD::Password::VERSION
12             });
13              
14             has ua => (is => 'rw', required => 1, lazy => 1,
15             default => sub {
16             my $self = shift;
17             HTTP::Tiny->new(
18             agent => $self->ua_agent,
19             timeout => 60,
20             );
21             }
22             );
23              
24             has username => (is => 'ro', isa => Str, required => 1);
25             has password => (is => 'ro', isa => Str, required => 1);
26              
27             has resource_id => (
28             is => 'ro',
29             isa => Str,
30             required => 1,
31             );
32              
33             has tenant_id => (
34             is => 'ro',
35             isa => Str,
36             required => 1,
37             default => sub {
38             $ENV{AZURE_TENANT_ID}
39             }
40             );
41              
42             has client_id => (
43             is => 'ro',
44             isa => Str,
45             required => 1,
46             default => sub {
47             $ENV{AZURE_CLIENT_ID}
48             }
49             );
50              
51             has ad_url => (
52             is => 'ro',
53             isa => Str,
54             default => sub {
55             'https://login.microsoftonline.com'
56             },
57             );
58              
59             has token_endpoint => (
60             is => 'ro',
61             isa => Str,
62             lazy => 1,
63             default => sub {
64             my $self = shift;
65             sprintf "%s/%s/oauth2/token", $self->ad_url, $self->tenant_id;
66             }
67             );
68              
69             sub access_token {
70 0     0 1   my $self = shift;
71 0           $self->_refresh;
72 0           $self->current_creds->{ access_token };
73             }
74              
75             has current_creds => (is => 'rw');
76              
77             has expiration => (
78             is => 'rw',
79             isa => Int,
80             lazy => 1,
81             default => sub { 0 }
82             );
83              
84             sub _refresh_from_cache {
85 0     0     my $self = shift;
86             #TODO: implement caching strategy
87 0           return undef;
88             }
89              
90             sub _save_to_cache {
91 0     0     my $self = shift;
92             #TODO: implement caching strategy
93             }
94              
95             sub _refresh {
96 0     0     my $self = shift;
97              
98 0 0         if (not defined $self->current_creds) {
99 0           $self->_refresh_from_cache;
100 0 0         return $self->current_creds if (defined $self->current_creds);
101             }
102              
103 0 0         return if $self->expiration >= time;
104              
105 0           my $auth_response = $self->ua->post_form(
106             $self->token_endpoint,
107             {
108             grant_type => 'password',
109             client_id => $self->client_id,
110             resource => $self->resource_id,
111             username => $self->username,
112             password => $self->password,
113             }
114             );
115              
116 0 0         if (not $auth_response->{ success }) {
117             Azure::AD::RemoteError->throw(
118             message => $auth_response->{ content },
119             code => 'GetClientCredentialsFailed',
120             status => $auth_response->{ status }
121 0           );
122             }
123              
124 0           my $auth = decode_json($auth_response->{content});
125 0           $self->current_creds($auth);
126 0           $self->expiration($auth->{ expires_on });
127 0           $self->_save_to_cache;
128             }
129              
130             1;
131              
132             =encoding UTF-8
133              
134             =head1 NAME
135              
136             Azure::AD::Password - Azure AD Password authentication flow
137              
138             =head1 SYNOPSIS
139              
140             use Azure::AD::Password;
141             my $creds = Azure::AD::Password->new(
142             resource_id => 'https://management.core.windows.net/',
143             client_id => '',
144             tenant_id => '',
145             username => '',
146             password => '',
147             );
148             say $creds->access_token;
149              
150             =head1 DESCRIPTION
151              
152             Implements the Azure AD Password flow. In general Microsoft does not advise
153             customers to use it as it's less secure than the other flows, and it is not
154             compatible with conditional access. See L for more
155             information and alternative flows.
156              
157             =head1 ATTRIBUTES
158              
159             =head2 resource_id
160              
161             The URL for which you want a token extended (the URL of the service which you want
162             to obtain a token for).
163              
164             C for using the MS Graph API
165              
166             C for using the Azure Management APIs
167              
168             =head2 tenant_id
169              
170             The ID of the Azure Active Directory Tenant
171              
172             =head2 client_id
173              
174             The Client ID (also referred to as the Application ID) of an application. In the
175             case of the password flow, the application should be of type C.
176              
177             =head2 username
178              
179             The user name to use for authentication.
180              
181             =head2 password
182              
183             The password of the user.
184              
185             =head2 ad_url
186              
187             This defaults to C, and generally doesn't need to
188             be specified. Azure AD has more endpoints for some clouds:
189              
190             C China Cloud
191              
192             C US Gov Cloud
193              
194             C German Cloud
195              
196             =head1 METHODS
197              
198             =head2 access_token
199              
200             Returns the access token that has to be sent to the APIs you want to access. This
201             is normally sent in the Authentication header of HTTPS requests as a Bearer token.
202              
203             The access_token is cached in the object as long as it's valid, so subsequent calls
204             to access_token will return the appropiate token without reauthenticating to Azure AD.
205             If the token has expired, access_token will call Azure AD to obtain a new token transparently.
206              
207             Example usage:
208              
209             my $auth = Azure::AD::Password->new(...);
210              
211             use HTTP::Tiny;
212             my $ua = HTTP::Tiny->new;
213             my $response = $ua->get(
214             'http://aservice.com/orders/list',
215             {
216             headers => { Authorization => 'Bearer ' . $auth->access_token }
217             }
218             );
219              
220             =head1 SEE ALSO
221              
222             L
223              
224             =head1 COPYRIGHT and LICENSE
225              
226             Copyright (c) 2018 by CAPSiDE
227             This code is distributed under the Apache 2 License. The full text of the
228             license can be found in the LICENSE file included with this module.
229              
230             =cut