Skip to content

Commit 4082afa

Browse files
authored
Merge pull request #183 from waterkip/GH-authnrequest-add_scoping
Add scoping to Net::SAML2::Protocol::AuthnRequest
2 parents e31b8ca + 2d815e0 commit 4082afa

File tree

8 files changed

+129
-103
lines changed

8 files changed

+129
-103
lines changed

Makefile.PL

+1-3
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ my %WriteMakefileArgs = (
5454
"XML::LibXML" => 0,
5555
"XML::LibXML::XPathContext" => 0,
5656
"XML::Sig" => "0.64",
57-
"XML::Writer" => "0.625",
5857
"base" => 0,
5958
"namespace::autoclean" => 0,
6059
"strict" => 0,
@@ -77,7 +76,7 @@ my %WriteMakefileArgs = (
7776
"URI::URL" => 0,
7877
"XML::LibXML::XPathContext" => 0
7978
},
80-
"VERSION" => "0.71",
79+
"VERSION" => "0.72",
8180
"test" => {
8281
"TESTS" => "t/*.t t/author/*.t"
8382
}
@@ -136,7 +135,6 @@ my %FallbackPrereqs = (
136135
"XML::LibXML" => 0,
137136
"XML::LibXML::XPathContext" => 0,
138137
"XML::Sig" => "0.64",
139-
"XML::Writer" => "0.625",
140138
"base" => 0,
141139
"namespace::autoclean" => 0,
142140
"strict" => 0,

README

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ NAME
22
Net::SAML2 - SAML2 bindings and protocol implementation
33

44
VERSION
5-
version 0.71
5+
version 0.72
66

77
SYNOPSIS
88
See TUTORIAL.md for implementation documentation and

cpanfile

-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ requires "XML::Generator" => "1.13";
3838
requires "XML::LibXML" => "0";
3939
requires "XML::LibXML::XPathContext" => "0";
4040
requires "XML::Sig" => "0.64";
41-
requires "XML::Writer" => "0.625";
4241
requires "base" => "0";
4342
requires "namespace::autoclean" => "0";
4443
requires "perl" => "5.012";

dist.ini

-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ skip = Saml2Test
5050
[Prereqs / RuntimeRequires]
5151
XML::Enc = 0.13
5252
XML::Sig = 0.64
53-
XML::Writer = 0.625
5453
; Here because otherwise only on test you get to pull in this dependency
5554
; which might only be an issue with cpm or if you run --no-test with cpanm
5655
XML::LibXML::XPathContext = 0

lib/Net/SAML2/Protocol/AuthnRequest.pm

+99-87
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
1-
use strict;
2-
use warnings;
31
package Net::SAML2::Protocol::AuthnRequest;
4-
# VERSION
5-
62
use Moose;
7-
use MooseX::Types::URI qw/ Uri /;
3+
4+
# VERSION
5+
use MooseX::Types::URI qw/ Uri /;
86
use MooseX::Types::Common::String qw/ NonEmptySimpleStr /;
9-
use XML::Writer 0.625;
10-
use List::Util qw(any);
7+
use XML::Generator;
8+
use List::Util qw(any);
9+
use URN::OASIS::SAML2 qw(:urn BINDING_HTTP_POST);
1110

1211
with 'Net::SAML2::Role::ProtocolMessage';
1312

@@ -54,7 +53,7 @@ Net::SAML2::Protocol::AuthnRequest - SAML2 AuthnRequest object
5453
Constructor. Creates an instance of the AuthnRequest object.
5554
5655
Important Note: Best practice is to always do this first. While it is possible
57-
to call as_xml() first you do not have to set the id as it will be set for you
56+
to call C<as_xml()> first you do not have to set the id as it will be set for you
5857
automatically.
5958
6059
However tracking the id is important for security to ensure that the response
@@ -64,19 +63,25 @@ Arguments:
6463
6564
=over
6665
67-
=item B<nameidpolicy_format>
66+
=item nameidpolicy_format
6867
6968
Format attribute for NameIDPolicy
7069
71-
=item B<AuthnContextClassRef>, B<AuthnContextDeclRef>
70+
=item AuthnContextClassRef, <AuthnContextDeclRef
71+
72+
Each one is an arrayref containing values for AuthnContextClassRef and
73+
AuthnContextDeclRef. If any is populated, the RequestedAuthnContext will be
74+
included in the request.
7275
73-
Each one is an arrayref containing values for AuthnContextClassRef and AuthnContextDeclRef.
74-
If any is populated, the RequestedAuthnContext will be included in the request.
76+
=item RequestedAuthnContext_Comparison
7577
76-
=item B<RequestedAuthnContext_Comparison>
78+
Value for the I<Comparison> attribute in case I<RequestedAuthnContext> is
79+
included (see above). Default value is I<exact>.
7780
78-
Value for the I<Comparison> attribute in case I<RequestedAuthnContext> is included
79-
(see above). Default value is I<exact>.
81+
=item identity_providers
82+
83+
An arrayref of Identity providers, if used the Scoping element is added to the
84+
XML
8085
8186
=back
8287
@@ -102,46 +107,46 @@ has 'nameid_allow_create' => (
102107
);
103108

104109
has 'assertion_url' => (
105-
isa => Uri,
106-
is => 'rw',
107-
coerce => 1,
110+
isa => Uri,
111+
is => 'rw',
112+
coerce => 1,
108113
predicate => 'has_assertion_url',
109114
);
110115

111116
has 'assertion_index' => (
112-
isa => 'Int',
113-
is => 'rw',
117+
isa => 'Int',
118+
is => 'rw',
114119
predicate => 'has_assertion_index',
115120
);
116121

117122
has 'attribute_index' => (
118-
isa => 'Int',
119-
is => 'rw',
123+
isa => 'Int',
124+
is => 'rw',
120125
predicate => 'has_attribute_index',
121126
);
122127

123128
has 'protocol_binding' => (
124-
isa => Uri,
125-
is => 'rw',
126-
coerce => 1,
129+
isa => Uri,
130+
is => 'rw',
131+
coerce => 1,
127132
predicate => 'has_protocol_binding',
128133
);
129134
has 'provider_name' => (
130-
isa => 'Str',
131-
is => 'rw',
135+
isa => 'Str',
136+
is => 'rw',
132137
predicate => 'has_provider_name',
133138
);
134139

135140
has 'AuthnContextClassRef' => (
136-
isa => 'ArrayRef[Str]',
137-
is => 'rw',
138-
default => sub {[]}
141+
isa => 'ArrayRef[Str]',
142+
is => 'rw',
143+
default => sub { [] }
139144
);
140145

141146
has 'AuthnContextDeclRef' => (
142-
isa => 'ArrayRef[Str]',
143-
is => 'rw',
144-
default => sub {[]}
147+
isa => 'ArrayRef[Str]',
148+
is => 'rw',
149+
default => sub { [] }
145150
);
146151

147152
has 'RequestedAuthnContext_Comparison' => (
@@ -151,17 +156,23 @@ has 'RequestedAuthnContext_Comparison' => (
151156
);
152157

153158
has 'force_authn' => (
154-
isa => 'Bool',
155-
is => 'ro',
159+
isa => 'Bool',
160+
is => 'ro',
156161
predicate => 'has_force_authn',
157162
);
158163

159164
has 'is_passive' => (
160-
isa => 'Bool',
161-
is => 'ro',
165+
isa => 'Bool',
166+
is => 'ro',
162167
predicate => 'has_is_passive',
163168
);
164169

170+
has identity_providers => (
171+
isa => 'ArrayRef[Str]',
172+
is => 'ro',
173+
predicate => 'has_identity_providers',
174+
);
175+
165176
around BUILDARGS => sub {
166177
my $orig = shift;
167178
my $self = shift;
@@ -180,20 +191,13 @@ Returns the AuthnRequest as XML.
180191
181192
=cut
182193

183-
my $saml = 'urn:oasis:names:tc:SAML:2.0:assertion';
184-
my $samlp = 'urn:oasis:names:tc:SAML:2.0:protocol';
194+
my $samlp = ['samlp' => URN_PROTOCOL];
195+
my $saml = ['samlp' => URN_ASSERTION];
185196

186197
sub as_xml {
187198
my ($self) = @_;
188-
my $x = XML::Writer->new(
189-
OUTPUT => 'self',
190-
NAMESPACES => 1,
191-
FORCED_NS_DECLS => [$saml, $samlp],
192-
PREFIX_MAP => {
193-
$saml => 'saml2',
194-
$samlp => 'saml2p'
195-
}
196-
);
199+
200+
my $x = XML::Generator->new(':std');
197201

198202
my %req_atts = (
199203
ID => $self->id,
@@ -203,9 +207,8 @@ sub as_xml {
203207

204208
my %issuer_attrs = ();
205209

206-
my %protocol_bindings = (
207-
'HTTP-POST' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'
208-
);
210+
my %protocol_bindings
211+
= ('HTTP-POST' => BINDING_HTTP_POST);
209212

210213
my %att_map = (
211214
'assertion_url' => 'AssertionConsumerServiceURL',
@@ -235,7 +238,7 @@ sub as_xml {
235238
$req_atts{ $att_map{$opt} } = $protocol_bindings{$val};
236239
}
237240
elsif (any { $opt eq $_ } qw(force_authn is_passive)) {
238-
$req_atts{ $att_map{$opt} } = ( $val ? 'true' : 'false' );
241+
$req_atts{ $att_map{$opt} } = ($val ? 'true' : 'false');
239242
}
240243
else {
241244
$req_atts{ $att_map{$opt} } = $val;
@@ -250,59 +253,68 @@ sub as_xml {
250253
$issuer_attrs{ $att_map{$opt} } = $val;
251254
}
252255

253-
$x->startTag([$samlp, 'AuthnRequest'], %req_atts);
254-
$x->dataElement([$saml, 'Issuer'], $self->issuer, %issuer_attrs);
256+
return $x->AuthnRequest($samlp,
257+
\%req_atts,
258+
$x->Issuer($saml, \%issuer_attrs, $self->issuer),
259+
$self->_set_name_id($x),
260+
$self->_set_name_policy_format($x),
261+
$self->_set_requested_authn_context($x),
262+
$self->_set_scoping($x),
263+
);
264+
265+
}
255266

256-
$self->_set_name_id($x);
257-
$self->_set_name_policy_format($x);
258-
$self->_set_requested_authn_context($x);
267+
sub _set_scoping {
268+
my $self = shift;
269+
return unless $self->has_identity_providers;
270+
my $x = shift;
259271

260-
$x->endTag();
261-
$x->end();
272+
my @providers = map { $x->IDPEntry($samlp, { ProviderID => $_ }) }
273+
@{ $self->identity_providers };
274+
return $x->Scoping($samlp, $x->IDPList($samlp, @providers));
262275
}
263276

264277
sub _set_name_id {
265-
my ($self, $x) = @_;
266-
return if !$self->has_nameid;
267-
$x->startTag([$saml, 'Subject']);
268-
$x->dataElement([$saml, 'NameID'], undef, NameQualifier => $self->nameid);
269-
$x->endTag();
270-
return;
278+
my $self = shift;
279+
return unless $self->has_nameid;
280+
my $x = shift;
281+
return $x->Subject($saml, $x->NameID($saml, {NameQualifier => $self->nameid}));
271282
}
272283

273284
sub _set_name_policy_format {
274-
my ($self, $x) = @_;
275-
return if !$self->has_nameidpolicy_format;
276-
277-
$x->dataElement([$samlp, 'NameIDPolicy'],
278-
undef,
279-
Format => $self->nameidpolicy_format,
280-
$self->has_nameid_allow_create
285+
my $self = shift;
286+
return unless $self->has_nameidpolicy_format;
287+
my $x = shift;
288+
return $x->NameIDPolicy(
289+
$samlp,
290+
{
291+
Format => $self->nameidpolicy_format,
292+
$self->has_nameid_allow_create
281293
? (AllowCreate => $self->nameid_allow_create)
282294
: (),
295+
}
283296
);
284-
return;
297+
285298
}
286299

287300
sub _set_requested_authn_context {
288-
my ($self, $x) = @_;
301+
my ($self, $x) = @_;
289302

290-
if (!@{ $self->AuthnContextClassRef } && !@{ $self->AuthnContextDeclRef })
291-
{
292-
return;
293-
}
303+
return
304+
if !@{ $self->AuthnContextClassRef }
305+
&& !@{ $self->AuthnContextDeclRef };
294306

295-
$x->startTag([$samlp, 'RequestedAuthnContext'],
296-
Comparison => $self->RequestedAuthnContext_Comparison);
307+
my @class = map { $x->AuthnContextClassRef($saml, undef, $_) }
308+
@{ $self->AuthnContextClassRef };
297309

298-
foreach my $ref (@{ $self->AuthnContextClassRef }) {
299-
$x->dataElement([$saml, 'AuthnContextClassRef'], $ref);
300-
}
301-
foreach my $ref (@{ $self->AuthnContextDeclRef }) {
302-
$x->dataElement([$saml, 'AuthnContextDeclRef'], $ref);
303-
}
310+
my @decl = map { $x->AuthnContextDeclRef($saml, undef, $_) }
311+
@{ $self->AuthnContextDeclRef };
304312

305-
$x->endTag();
313+
return $x->RequestedAuthnContext(
314+
$samlp,
315+
{ Comparison => $self->RequestedAuthnContext_Comparison },
316+
@class, @decl
317+
);
306318
}
307319

308320
__PACKAGE__->meta->make_immutable;

t/06-redirect-binding.t

+5-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,11 @@ my $authnreq = $sp->authn_request(
2929
$idp->format('persistent')
3030
)->as_xml;
3131

32-
my $xp = get_xpath($authnreq);
32+
my $xp = get_xpath(
33+
$authnreq,
34+
saml2p => 'urn:oasis:names:tc:SAML:2.0:protocol',
35+
saml => 'urn:oasis:names:tc:SAML:2.0:assertion',
36+
);
3337

3438
my $redirect = $sp->sso_redirect_binding($idp, 'SAMLRequest');
3539
isa_ok($redirect, 'Net::SAML2::Binding::Redirect');

t/09-authn-request.t

+14
Original file line numberDiff line numberDiff line change
@@ -183,5 +183,19 @@ $override->override('Net::SAML2::Protocol::AuthnRequest::_build_id' =>
183183
);
184184
}
185185

186+
{
187+
188+
my ($ar, $xp) = net_saml2_authnreq(
189+
identity_providers => [qw(one two three)]
190+
);
191+
192+
my $nodes = $xp->findnodes(
193+
'/samlp:AuthnRequest/samlp:Scoping/samlp:IDPList/samlp:IDPEntry');
194+
is($nodes->size, 3, "Found three IDP entries");
195+
196+
cmp_deeply([$nodes->map(sub { return $_->getAttribute('ProviderID') })],
197+
[qw(one two three)], "... and the correct provider IDs found");
198+
199+
}
186200

187201
done_testing;

0 commit comments

Comments
 (0)