line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Azure::AD::ClientCredentialsV2; |
2
|
1
|
|
|
1
|
|
1073
|
use Moo; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
6
|
|
3
|
1
|
|
|
1
|
|
273
|
use Azure::AD::Errors; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
33
|
|
4
|
1
|
|
|
1
|
|
5
|
use Types::Standard qw/Str Int InstanceOf/; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
9
|
|
5
|
1
|
|
|
1
|
|
689
|
use JSON::MaybeXS; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
56
|
|
6
|
1
|
|
|
1
|
|
6
|
use HTTP::Tiny; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
558
|
|
7
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
our $VERSION = '0.01'; |
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
has ua_agent => ( |
11
|
|
|
|
|
|
|
is => 'ro', |
12
|
|
|
|
|
|
|
isa => Str, |
13
|
|
|
|
|
|
|
default => sub { |
14
|
|
|
|
|
|
|
'Azure::AD::ClientCredentialsV2 ' . $Azure::AD::ClientCredentialsV2::VERSION |
15
|
|
|
|
|
|
|
} |
16
|
|
|
|
|
|
|
); |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
has ua => ( |
19
|
|
|
|
|
|
|
is => 'rw', |
20
|
|
|
|
|
|
|
required => 1, |
21
|
|
|
|
|
|
|
lazy => 1, |
22
|
|
|
|
|
|
|
default => sub { |
23
|
|
|
|
|
|
|
my $self = shift; |
24
|
|
|
|
|
|
|
HTTP::Tiny->new( |
25
|
|
|
|
|
|
|
agent => $self->ua_agent, |
26
|
|
|
|
|
|
|
timeout => 60, |
27
|
|
|
|
|
|
|
); |
28
|
|
|
|
|
|
|
} |
29
|
|
|
|
|
|
|
); |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
has scope => ( |
32
|
|
|
|
|
|
|
is => 'ro', |
33
|
|
|
|
|
|
|
isa => Str, |
34
|
|
|
|
|
|
|
required => 1, |
35
|
|
|
|
|
|
|
); |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
has tenant_id => ( |
38
|
|
|
|
|
|
|
is => 'ro', |
39
|
|
|
|
|
|
|
isa => Str, |
40
|
|
|
|
|
|
|
required => 1, |
41
|
|
|
|
|
|
|
default => sub { |
42
|
|
|
|
|
|
|
$ENV{AZURE_TENANT_ID} |
43
|
|
|
|
|
|
|
} |
44
|
|
|
|
|
|
|
); |
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
has client_id => ( |
47
|
|
|
|
|
|
|
is => 'ro', |
48
|
|
|
|
|
|
|
isa => Str, |
49
|
|
|
|
|
|
|
required => 1, |
50
|
|
|
|
|
|
|
default => sub { |
51
|
|
|
|
|
|
|
$ENV{AZURE_CLIENT_ID} |
52
|
|
|
|
|
|
|
} |
53
|
|
|
|
|
|
|
); |
54
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
has secret_id => ( |
56
|
|
|
|
|
|
|
is => 'ro', |
57
|
|
|
|
|
|
|
isa => Str, |
58
|
|
|
|
|
|
|
required => 1, |
59
|
|
|
|
|
|
|
default => sub { |
60
|
|
|
|
|
|
|
$ENV{AZURE_SECRET_ID} |
61
|
|
|
|
|
|
|
} |
62
|
|
|
|
|
|
|
); |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
has ad_url => ( |
65
|
|
|
|
|
|
|
is => 'ro', |
66
|
|
|
|
|
|
|
isa => Str, |
67
|
|
|
|
|
|
|
default => sub { |
68
|
|
|
|
|
|
|
'https://login.microsoftonline.com' |
69
|
|
|
|
|
|
|
}, |
70
|
|
|
|
|
|
|
); |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
has token_endpoint => ( |
73
|
|
|
|
|
|
|
is => 'ro', |
74
|
|
|
|
|
|
|
isa => Str, |
75
|
|
|
|
|
|
|
lazy => 1, |
76
|
|
|
|
|
|
|
default => sub { |
77
|
|
|
|
|
|
|
my $self = shift; |
78
|
|
|
|
|
|
|
sprintf "%s/%s/oauth2/v2.0/token", $self->ad_url, $self->tenant_id; |
79
|
|
|
|
|
|
|
} |
80
|
|
|
|
|
|
|
); |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
sub access_token { |
83
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
84
|
0
|
|
|
|
|
|
$self->_refresh; |
85
|
0
|
|
|
|
|
|
$self->current_creds->{ access_token }; |
86
|
|
|
|
|
|
|
} |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
has current_creds => (is => 'rw'); |
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
has expiration => ( |
91
|
|
|
|
|
|
|
is => 'rw', |
92
|
|
|
|
|
|
|
isa => Int, |
93
|
|
|
|
|
|
|
lazy => 1, |
94
|
|
|
|
|
|
|
default => sub { 0 } |
95
|
|
|
|
|
|
|
); |
96
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
sub _refresh_from_cache { |
98
|
0
|
|
|
0
|
|
|
my $self = shift; |
99
|
|
|
|
|
|
|
#TODO: implement caching strategy |
100
|
0
|
|
|
|
|
|
return undef; |
101
|
|
|
|
|
|
|
} |
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
sub _save_to_cache { |
104
|
0
|
|
|
0
|
|
|
my $self = shift; |
105
|
|
|
|
|
|
|
#TODO: implement caching strategy |
106
|
|
|
|
|
|
|
} |
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
sub _refresh { |
109
|
0
|
|
|
0
|
|
|
my $self = shift; |
110
|
|
|
|
|
|
|
|
111
|
0
|
0
|
|
|
|
|
if (not defined $self->current_creds) { |
112
|
0
|
|
|
|
|
|
$self->_refresh_from_cache; |
113
|
0
|
0
|
|
|
|
|
return $self->current_creds if (defined $self->current_creds); |
114
|
|
|
|
|
|
|
} |
115
|
|
|
|
|
|
|
|
116
|
0
|
0
|
|
|
|
|
return if $self->expiration >= time; |
117
|
|
|
|
|
|
|
|
118
|
0
|
|
|
|
|
|
my $auth_response = $self->ua->post_form( |
119
|
|
|
|
|
|
|
$self->token_endpoint, |
120
|
|
|
|
|
|
|
{ |
121
|
|
|
|
|
|
|
grant_type => 'client_credentials', |
122
|
|
|
|
|
|
|
client_id => $self->client_id, |
123
|
|
|
|
|
|
|
client_secret => $self->secret_id, |
124
|
|
|
|
|
|
|
scope => $self->scope, |
125
|
|
|
|
|
|
|
} |
126
|
|
|
|
|
|
|
); |
127
|
|
|
|
|
|
|
|
128
|
0
|
0
|
|
|
|
|
if (not $auth_response->{success}) { |
129
|
|
|
|
|
|
|
Azure::AD::RemoteError->throw( |
130
|
|
|
|
|
|
|
message => $auth_response->{content}, |
131
|
|
|
|
|
|
|
code => 'GetClientCredentialsFailed', |
132
|
|
|
|
|
|
|
status => $auth_response->{status} |
133
|
0
|
|
|
|
|
|
); |
134
|
|
|
|
|
|
|
} |
135
|
|
|
|
|
|
|
|
136
|
0
|
|
|
|
|
|
my $auth = decode_json($auth_response->{content}); |
137
|
0
|
|
|
|
|
|
$self->current_creds($auth); |
138
|
0
|
|
|
|
|
|
$self->expiration($auth->{expires_in} + time); |
139
|
0
|
|
|
|
|
|
$self->_save_to_cache; |
140
|
|
|
|
|
|
|
} |
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
1; |
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
=encoding UTF-8 |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
=head1 NAME |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
Azure::AD::ClientCredentialsV2 - Azure AD Client Credentials authentication flow |
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
=head1 SYNOPSIS |
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
use Azure::AD::ClientCredentialsV2; |
153
|
|
|
|
|
|
|
my $creds = Azure::AD::ClientCredentialsV2->new( |
154
|
|
|
|
|
|
|
resource_id => 'https://management.core.windows.net/', |
155
|
|
|
|
|
|
|
client_id => '', |
156
|
|
|
|
|
|
|
secret_id => '', |
157
|
|
|
|
|
|
|
tenant_id => '', |
158
|
|
|
|
|
|
|
); |
159
|
|
|
|
|
|
|
say $creds->access_token; |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
=head1 DESCRIPTION |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
Implements the Azure AD Client Credentials flow using the V2 Oauth endpoint. See L for more |
164
|
|
|
|
|
|
|
information and alternative flows. |
165
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
=head1 ATTRIBUTES |
167
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
=head2 scope |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
Defines the set of permissions being requested by the application. Scopes can be either static (using .default) or dynamic. This set can include the OpenID Connect scopes (openid, profile, email). If you need application permissions, you must use .default to request the statically configured list of permissions. |
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
=head2 tenant_id |
173
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
The ID of the Azure Active Directory Tenant that you want to request permission from. It can be provided in a GUID or friendly name format. |
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
=head2 client_id |
177
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
The Client ID (also referred to as the Application ID) of an application |
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
=head2 secret_id |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
A Key assigned to the Client Id. |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
=head2 ad_url |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
This defaults to C, and generally doesn't need to |
187
|
|
|
|
|
|
|
be specified. Azure AD has more endpoints for some clouds: |
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
C China Cloud |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
C US Gov Cloud |
192
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
C German Cloud |
194
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
=head1 METHODS |
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
=head2 access_token |
198
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
Returns the access token that has to be sent to the APIs you want to access. This |
200
|
|
|
|
|
|
|
is normally sent in the Authentication header of HTTPS requests as a Bearer token. |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
The access_token is cached in the object as long as it's valid, so subsequent calls |
203
|
|
|
|
|
|
|
to access_token will return the appropiate token without reauthenticating to Azure AD. |
204
|
|
|
|
|
|
|
If the token has expired, access_token will call Azure AD to obtain a new token transparently. |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
Example usage: |
207
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
my $auth = Azure::AD::ClientCredentialsV2->new(...); |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
use HTTP::Tiny; |
211
|
|
|
|
|
|
|
my $ua = HTTP::Tiny->new; |
212
|
|
|
|
|
|
|
my $response = $ua->get( |
213
|
|
|
|
|
|
|
'http://aservice.com/orders/list', |
214
|
|
|
|
|
|
|
{ |
215
|
|
|
|
|
|
|
headers => { Authorization => 'Bearer ' . $auth->access_token } |
216
|
|
|
|
|
|
|
} |
217
|
|
|
|
|
|
|
); |
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
=head1 SEE ALSO |
220
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
L |
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
=head1 COPYRIGHT and LICENSE |
224
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
Copyright (c) 2020 by Jose Luis Martinez |
226
|
|
|
|
|
|
|
|
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 |