| 212 | | my $ures = URI::Fetch->fetch($url, |
| 213 | | UserAgent => $self->ua, |
| 214 | | Cache => $self->cache, |
| 215 | | ContentAlterHook => $hook, |
| 216 | | ) |
| 217 | | or return $self->_fail("url_fetch_error", "Error fetching URL: " . URI::Fetch->errstr); |
| 218 | | |
| 219 | | # who actually uses HTTP gone response status? uh, nobody. |
| 220 | | if ($ures->status == URI::Fetch::URI_GONE()) { |
| 221 | | return $self->_fail("url_gone", "URL is no longer available"); |
| 222 | | } |
| 223 | | |
| 224 | | my $res = $ures->http_response; |
| 225 | | $$final_url_ref = $res->request->uri->as_string; |
| 226 | | |
| 227 | | return $ures->content; |
| | 212 | my $res = Net::OpenID::URIFetch->fetch($url, $self, $hook); |
| | 213 | |
| | 214 | $$final_url_ref = $res->final_uri; |
| | 215 | |
| | 216 | return $res ? $res->content : undef; |
| 533 | | push @discovered_endpoints, { |
| 534 | | uri => $sem_info->{"openid2.provider"}, |
| 535 | | version => 2, |
| 536 | | final_url => $final_url, |
| 537 | | delegate => $sem_info->{"openid2.local_id"}, |
| 538 | | sem_info => $sem_info, |
| 539 | | mechanism => "HTML", |
| 540 | | }; |
| | 524 | unless (defined($force_version) && $force_version != 2) { |
| | 525 | push @discovered_endpoints, { |
| | 526 | uri => $sem_info->{"openid2.provider"}, |
| | 527 | version => 2, |
| | 528 | final_url => $final_url, |
| | 529 | delegate => $sem_info->{"openid2.local_id"}, |
| | 530 | sem_info => $sem_info, |
| | 531 | mechanism => "HTML", |
| | 532 | }; |
| | 533 | } |
| 543 | | push @discovered_endpoints, { |
| 544 | | uri => $sem_info->{"openid.server"}, |
| 545 | | version => 1, |
| 546 | | final_url => $final_url, |
| 547 | | delegate => $sem_info->{"openid.delegate"}, |
| 548 | | sem_info => $sem_info, |
| 549 | | mechanism => "HTML", |
| 550 | | }; |
| | 536 | unless (defined($force_version) && $force_version != 1) { |
| | 537 | push @discovered_endpoints, { |
| | 538 | uri => $sem_info->{"openid.server"}, |
| | 539 | version => 1, |
| | 540 | final_url => $final_url, |
| | 541 | delegate => $sem_info->{"openid.delegate"}, |
| | 542 | sem_info => $sem_info, |
| | 543 | mechanism => "HTML", |
| | 544 | }; |
| | 545 | } |
| | 656 | |
| | 657 | # In version 1, we have to assume that the primary server |
| | 658 | # found during discovery is the one sending us this message. |
| | 659 | $possible_endpoints = $self->_discover_acceptable_endpoints($real_ident, force_version => 1); |
| | 660 | |
| | 661 | if ($possible_endpoints && @$possible_endpoints) { |
| | 662 | $possible_endpoints = [ $possible_endpoints->[0] ]; |
| | 663 | $server = $possible_endpoints->[0]{uri}; |
| | 664 | } |
| | 665 | else { |
| | 666 | # We just fall out of here and bail out below for having no endpoints. |
| | 667 | } |
| | 671 | |
| | 672 | # In version 2, the OP tells us its URL. |
| | 673 | $server = $self->message("op_endpoint"); |
| | 674 | $possible_endpoints = $self->_discover_acceptable_endpoints($real_ident, force_version => 2); |
| | 675 | |
| | 676 | # FIXME: It kinda sucks that the above will always do both Yadis and HTML discovery, even though |
| | 677 | # in most cases only one will be in use. |
| | 678 | } |
| | 679 | |
| | 680 | $self->_debug("Server is $server"); |
| | 681 | |
| | 682 | unless ($possible_endpoints && @$possible_endpoints) { |
| | 683 | return $self->_fail("no_identity_server"); |
| 680 | | my $claimed_identity = $self->claimed_identity($real_ident); |
| 681 | | return $self->_fail("no_identity_server") unless $claimed_identity; |
| 682 | | |
| 683 | | # NOTE: Currently we're expecting the "primary" OP -- that is, the one that "wins" |
| 684 | | # when we do discovery -- to be the one that sends the response. Since we currently |
| 685 | | # don't support falling back to other providers in the XRD case, this should always |
| 686 | | # be a valid assumption unless this assersion request is unsolicited. |
| 687 | | # We'll also fail if the identifier's provider priorities are twiddled between |
| 688 | | # request and response, but that's unlikely enough that we're just going to ignore it. |
| 689 | | |
| 690 | | my $final_url = $claimed_identity->claimed_url; |
| 691 | | |
| 692 | | # OpenID 2.0 wants us to exclude the fragment part of the URL when doing equality checks |
| 693 | | my $a_ident_nofragment = $a_ident; |
| 694 | | my $real_ident_nofragment = $real_ident; |
| 695 | | my $final_url_nofragment = $final_url; |
| 696 | | if ($self->_message_version >= 2) { |
| 697 | | $a_ident_nofragment =~ s/\#.*$//x; |
| 698 | | $real_ident_nofragment =~ s/\#.*$//x; |
| 699 | | $final_url_nofragment =~ s/\#.*$//x; |
| 700 | | } |
| 701 | | return $self->_fail("unexpected_url_redirect") unless $final_url_nofragment eq $real_ident_nofragment; |
| 702 | | |
| 703 | | my $server = $claimed_identity->identity_server; |
| 704 | | |
| 705 | | # Protocol version must match |
| 706 | | return $self->_fail("protocol_version_incorrect") unless $claimed_identity->protocol_version == $self->_message_version; |
| 707 | | |
| 708 | | # if openid.delegate was used, check that it was done correctly |
| 709 | | if ($a_ident_nofragment ne $real_ident_nofragment) { |
| 710 | | my $delegate = $claimed_identity->delegated_url; |
| | 704 | my $last_error = undef; |
| | 705 | |
| | 706 | foreach my $endpoint (@$possible_endpoints) { |
| | 707 | my $final_url = $endpoint->{final_url}; |
| | 708 | my $endpoint_uri = $endpoint->{uri}; |
| | 709 | my $delegate = $endpoint->{delegate}; |
| | 710 | |
| | 711 | my $error = sub { |
| | 712 | $self->_debug("$endpoint_uri not acceptable: ".$_[0]); |
| | 713 | $last_error = $_[0]; |
| | 714 | }; |
| | 715 | |
| | 716 | # The endpoint_uri must match our $server |
| | 717 | if ($endpoint_uri ne $server) { |
| | 718 | $error->("server_not_allowed"); |
| | 719 | next; |
| | 720 | } |
| | 721 | |
| | 722 | # OpenID 2.0 wants us to exclude the fragment part of the URL when doing equality checks |
| 712 | | $a_ident_nofragment =~ s/\#.*$//; |
| 713 | | $self->_debug("verified_identity: verifying delegate $delegate for $a_ident_nofragment"); |
| 714 | | return $self->_fail("bogus_delegation") unless $delegate eq $a_ident; |
| | 724 | my $real_ident_nofragment = $real_ident; |
| | 725 | my $final_url_nofragment = $final_url; |
| | 726 | if ($self->_message_version >= 2) { |
| | 727 | $a_ident_nofragment =~ s/\#.*$//x; |
| | 728 | $real_ident_nofragment =~ s/\#.*$//x; |
| | 729 | $final_url_nofragment =~ s/\#.*$//x; |
| | 730 | } |
| | 731 | unless ($final_url_nofragment eq $real_ident_nofragment) { |
| | 732 | $error->("unexpected_url_redirect"); |
| | 733 | next; |
| | 734 | } |
| | 735 | |
| | 736 | # Protocol version must match |
| | 737 | unless ($endpoint->{version} == $self->_message_version) { |
| | 738 | $error->("protocol_version_incorrect"); |
| | 739 | next; |
| | 740 | } |
| | 741 | |
| | 742 | # if openid.delegate was used, check that it was done correctly |
| | 743 | if ($a_ident_nofragment ne $real_ident_nofragment) { |
| | 744 | unless ($delegate eq $a_ident_nofragment) { |
| | 745 | $error->("bogus_delegation"); |
| | 746 | next; |
| | 747 | } |
| | 748 | } |
| | 749 | |
| | 750 | # If we've got this far then we've found the right endpoint. |
| | 751 | |
| | 752 | $claimed_identity = Net::OpenID::ClaimedIdentity->new( |
| | 753 | identity => $endpoint->{final_url}, |
| | 754 | server => $endpoint->{uri}, |
| | 755 | consumer => $self, |
| | 756 | delegate => $endpoint->{delegate}, |
| | 757 | protocol_version => $endpoint->{version}, |
| | 758 | semantic_info => $endpoint->{sem_info}, |
| | 759 | ); |
| | 760 | last; |
| | 761 | |
| | 762 | } |
| | 763 | |
| | 764 | unless ($claimed_identity) { |
| | 765 | # We failed to find a good endpoint in the above loop, so |
| | 766 | # lets bail out. |
| | 767 | return $self->_fail($last_error); |
| | 776 | |
| | 777 | # Auth 2.0 requires certain keys to be signed. |
| | 778 | if ($self->_message_version >= 2) { |
| | 779 | my %signed_fields = map {$_ => 1} split /,/, $signed; |
| | 780 | my %unsigned_fields; |
| | 781 | # these fields must be signed unconditionally |
| | 782 | foreach my $f (qw/op_endpoint return_to response_nonce assoc_handle/) { |
| | 783 | $unsigned_fields{$f}++ if !$signed_fields{$f}; |
| | 784 | } |
| | 785 | # these fields must be signed if present |
| | 786 | foreach my $f (qw/claimed_id identity/) { |
| | 787 | next unless $self->args("openid.$f"); |
| | 788 | $unsigned_fields{$f}++ if !$signed_fields{$f}; |
| | 789 | } |
| | 790 | if (%unsigned_fields) { |
| | 791 | return $self->_fail( |
| | 792 | "unsigned_field", |
| | 793 | "Field(s) must be signed: " . join(", ", keys %unsigned_fields) |
| | 794 | ); |
| | 795 | } |
| | 796 | } |