| 1 | |
|---|
| 2 | package Net::OpenID::IndirectMessage; |
|---|
| 3 | |
|---|
| 4 | use strict; |
|---|
| 5 | use Carp; |
|---|
| 6 | use Net::OpenID::Consumer; |
|---|
| 7 | |
|---|
| 8 | sub new { |
|---|
| 9 | my $class = shift; |
|---|
| 10 | my $what = shift; |
|---|
| 11 | my %opts = @_; |
|---|
| 12 | |
|---|
| 13 | my $self = bless {}, $class; |
|---|
| 14 | |
|---|
| 15 | $self->{minimum_version} = delete $opts{minimum_version}; |
|---|
| 16 | |
|---|
| 17 | Carp::croak("Unknown options: " . join(", ", keys %opts)) if %opts; |
|---|
| 18 | |
|---|
| 19 | my $getter; |
|---|
| 20 | my $enumer; |
|---|
| 21 | if (ref $what eq "HASH") { |
|---|
| 22 | # In this case it's the caller's responsibility to determine |
|---|
| 23 | # whether the method is GET or POST. |
|---|
| 24 | $getter = sub { $what->{$_[0]}; }; |
|---|
| 25 | $enumer = sub { keys(%$what); }; |
|---|
| 26 | } |
|---|
| 27 | elsif (UNIVERSAL::isa($what, "CGI")) { |
|---|
| 28 | # CGI automatically does what we need when method is POST |
|---|
| 29 | $getter = sub { scalar $what->param($_[0]); }; |
|---|
| 30 | $enumer = sub { $what->param; }; |
|---|
| 31 | } |
|---|
| 32 | elsif (ref $what eq "Apache") { |
|---|
| 33 | my %get; |
|---|
| 34 | if ($what->method eq 'POST') { |
|---|
| 35 | %get = $what->content; |
|---|
| 36 | } |
|---|
| 37 | else { |
|---|
| 38 | %get = $what->args; |
|---|
| 39 | } |
|---|
| 40 | $getter = sub { $get{$_[0]}; }; |
|---|
| 41 | $enumer = sub { keys(%get); }; |
|---|
| 42 | } |
|---|
| 43 | elsif (ref $what eq "Apache::Request") { |
|---|
| 44 | # Apache::Request includes the POST and GET arguments in ->param |
|---|
| 45 | # when doing a POST request, which is close enough to what |
|---|
| 46 | # the spec requires. |
|---|
| 47 | $getter = sub { scalar $what->param($_[0]); }; |
|---|
| 48 | $enumer = sub { $what->param; }; |
|---|
| 49 | } |
|---|
| 50 | elsif (ref $what eq "CODE") { |
|---|
| 51 | $getter = $what; |
|---|
| 52 | # We can't enumerate with just a coderef. |
|---|
| 53 | # OpenID 2 spec only requires enumeration to support |
|---|
| 54 | # extension namespaces, so we don't care too much. |
|---|
| 55 | $enumer = sub { return (); }; |
|---|
| 56 | } |
|---|
| 57 | else { |
|---|
| 58 | $what = 'undef' if !defined $what; |
|---|
| 59 | Carp::croak("Unknown parameter type ($what)"); |
|---|
| 60 | } |
|---|
| 61 | $self->{getter} = $getter; |
|---|
| 62 | $self->{enumer} = $enumer; |
|---|
| 63 | |
|---|
| 64 | # Now some quick pre-configuration of a few bits |
|---|
| 65 | |
|---|
| 66 | # Is this an OpenID message at all? |
|---|
| 67 | # All OpenID messages have an openid.mode value... |
|---|
| 68 | return undef unless $self->get('mode'); |
|---|
| 69 | |
|---|
| 70 | # Is this an OpenID 2.0 message? |
|---|
| 71 | my $ns = $self->get('ns'); |
|---|
| 72 | |
|---|
| 73 | |
|---|
| 74 | # The 2.0 spec section 4.1.2 requires that we support these namespace values |
|---|
| 75 | # but act like it's a normal 1.1 request. |
|---|
| 76 | # We do this by just pretending that ns wasn't set at all. |
|---|
| 77 | if ($ns && ($ns eq 'http://openid.net/signon/1.1' || $ns eq 'http://openid.net/signon/1.0')) { |
|---|
| 78 | $ns = undef; |
|---|
| 79 | } |
|---|
| 80 | |
|---|
| 81 | if (defined($ns) && $ns eq OpenID::util::version_2_namespace()) { |
|---|
| 82 | $self->{protocol_version} = 2; |
|---|
| 83 | } |
|---|
| 84 | elsif (! defined($ns)) { |
|---|
| 85 | # No namespace at all means a 1.1 message |
|---|
| 86 | if (($self->{minimum_version}||0) <= 1) { |
|---|
| 87 | $self->{protocol_version} = 1; |
|---|
| 88 | } |
|---|
| 89 | else { |
|---|
| 90 | # Pretend we don't understand the message. |
|---|
| 91 | return undef; |
|---|
| 92 | } |
|---|
| 93 | } |
|---|
| 94 | else { |
|---|
| 95 | # Unknown version is the same as not being an OpenID message at all |
|---|
| 96 | return undef; |
|---|
| 97 | } |
|---|
| 98 | |
|---|
| 99 | # This will be populated in on demand |
|---|
| 100 | $self->{extension_prefixes} = undef; |
|---|
| 101 | |
|---|
| 102 | return $self; |
|---|
| 103 | } |
|---|
| 104 | |
|---|
| 105 | sub protocol_version { |
|---|
| 106 | return $_[0]->{protocol_version}; |
|---|
| 107 | } |
|---|
| 108 | |
|---|
| 109 | sub mode { |
|---|
| 110 | my $self = shift; |
|---|
| 111 | return $self->get('mode'); |
|---|
| 112 | } |
|---|
| 113 | |
|---|
| 114 | sub get { |
|---|
| 115 | my $self = shift; |
|---|
| 116 | my $key = shift or Carp::croak("No argument name supplied to get method"); |
|---|
| 117 | |
|---|
| 118 | # NOTE: There is intentionally no way to get all of the keys in the core |
|---|
| 119 | # namespace because that means we don't need to be able to enumerate |
|---|
| 120 | # to support the core protocol, and there is no requirement to enumerate |
|---|
| 121 | # anyway. |
|---|
| 122 | |
|---|
| 123 | # Arguments can only contain letters, numbers, underscores and dashes |
|---|
| 124 | Carp::croak("Invalid argument key $key") unless $key =~ /^[\w\-]+$/; |
|---|
| 125 | Carp::croak("Too many arguments") if scalar(@_); |
|---|
| 126 | |
|---|
| 127 | return $self->{getter}->("openid.$key"); |
|---|
| 128 | } |
|---|
| 129 | |
|---|
| 130 | sub raw_get { |
|---|
| 131 | my $self = shift; |
|---|
| 132 | my $key = shift or Carp::croak("No argument name supplied to raw_get method"); |
|---|
| 133 | |
|---|
| 134 | return $self->{getter}->($key); |
|---|
| 135 | } |
|---|
| 136 | |
|---|
| 137 | sub getter { |
|---|
| 138 | my $self = shift; |
|---|
| 139 | |
|---|
| 140 | return $self->{getter}; |
|---|
| 141 | } |
|---|
| 142 | |
|---|
| 143 | sub get_ext { |
|---|
| 144 | my $self = shift; |
|---|
| 145 | my $namespace = shift or Carp::croak("No namespace URI supplied to get_ext method"); |
|---|
| 146 | my $key = shift; |
|---|
| 147 | |
|---|
| 148 | Carp::croak("Too many arguments") if scalar(@_); |
|---|
| 149 | |
|---|
| 150 | $self->_compute_extension_prefixes() unless defined($self->{extension_prefixes}); |
|---|
| 151 | |
|---|
| 152 | my $alias = $self->{extension_prefixes}{$namespace}; |
|---|
| 153 | return $key ? undef : {} unless $alias; |
|---|
| 154 | |
|---|
| 155 | if ($key) { |
|---|
| 156 | return $self->{getter}->("openid.$alias.$key"); |
|---|
| 157 | } |
|---|
| 158 | else { |
|---|
| 159 | my $prefix = "openid.$alias."; |
|---|
| 160 | my $prefixlen = length($prefix); |
|---|
| 161 | my $ret = {}; |
|---|
| 162 | foreach my $key ($self->{enumer}->()) { |
|---|
| 163 | next unless substr($key, 0, $prefixlen) eq $prefix; |
|---|
| 164 | $ret->{substr($key, $prefixlen)} = $self->{getter}->($key); |
|---|
| 165 | } |
|---|
| 166 | return $ret; |
|---|
| 167 | } |
|---|
| 168 | } |
|---|
| 169 | |
|---|
| 170 | sub has_ext { |
|---|
| 171 | my $self = shift; |
|---|
| 172 | my $namespace = shift or Carp::croak("No namespace URI supplied to get_ext method"); |
|---|
| 173 | |
|---|
| 174 | Carp::croak("Too many arguments") if scalar(@_); |
|---|
| 175 | |
|---|
| 176 | $self->_compute_extension_prefixes() unless defined($self->{extension_prefixes}); |
|---|
| 177 | |
|---|
| 178 | return defined($self->{extension_prefixes}{$namespace}) ? 1 : 0; |
|---|
| 179 | } |
|---|
| 180 | |
|---|
| 181 | sub _compute_extension_prefixes { |
|---|
| 182 | my ($self) = @_; |
|---|
| 183 | |
|---|
| 184 | return unless $self->{enumer}; |
|---|
| 185 | |
|---|
| 186 | $self->{extension_prefixes} = {}; |
|---|
| 187 | if ($self->protocol_version != 1) { |
|---|
| 188 | foreach my $key ($self->{enumer}->()) { |
|---|
| 189 | next unless $key =~ /^openid\.ns\.(\w+)$/; |
|---|
| 190 | my $alias = $1; |
|---|
| 191 | my $uri = $self->{getter}->($key); |
|---|
| 192 | $self->{extension_prefixes}{$uri} = $alias; |
|---|
| 193 | } |
|---|
| 194 | } |
|---|
| 195 | else { |
|---|
| 196 | # Synthesize the SREG namespace as it was used in OpenID 1.1 |
|---|
| 197 | $self->{extension_prefixes}{"http://openid.net/extensions/sreg/1.1"} = "sreg"; |
|---|
| 198 | } |
|---|
| 199 | } |
|---|
| 200 | |
|---|
| 201 | 1; |
|---|
| 202 | |
|---|
| 203 | =head1 NAME |
|---|
| 204 | |
|---|
| 205 | Net::OpenID::IndirectMessage - Class representing a collection of namespaced arguments |
|---|
| 206 | |
|---|
| 207 | =head1 DESCRIPTION |
|---|
| 208 | |
|---|
| 209 | This class acts as an abstraction layer over a collection of flat URL arguments |
|---|
| 210 | which supports namespaces as defined by the OpenID Auth 2.0 specification. |
|---|
| 211 | |
|---|
| 212 | It also recognises when its is given OpenID 1.1 non-namespaced arguments and |
|---|
| 213 | acts as if the relevant namespaces were present. In this case, it only |
|---|
| 214 | supports the basic OpenID 1.1 arguments and the extension arguments |
|---|
| 215 | for Simple Registration. |
|---|
| 216 | |
|---|
| 217 | This class can operate on a normal hashref, a L<CGI> object, an L<Apache> |
|---|
| 218 | object, an L<Apache::Request> object or an arbitrary C<CODE> ref that takes |
|---|
| 219 | a key name as its first parameter and returns a value. However, |
|---|
| 220 | if you use a coderef then extension arguments are not supported. |
|---|
| 221 | |
|---|
| 222 | If you pass in a hashref or a coderef it is your responsibility as the caller |
|---|
| 223 | to check the HTTP request method and pass in the correct set of arguments. If |
|---|
| 224 | you use an Apache, Apache::Request or CGI object then this module will do |
|---|
| 225 | the right thing automatically. |
|---|
| 226 | |
|---|
| 227 | =head1 SYNOPSIS |
|---|
| 228 | |
|---|
| 229 | use Net::OpenID::IndirectMessage; |
|---|
| 230 | |
|---|
| 231 | # Pass in something suitable for the underlying flat dictionary. |
|---|
| 232 | # Will return an instance if the request arguments can be understood |
|---|
| 233 | # as a supported OpenID Message format. |
|---|
| 234 | # Will return undef if this doesn't seem to be an OpenID Auth message. |
|---|
| 235 | # Will croak if the $argumenty_thing is not of a suitable type. |
|---|
| 236 | my $args = Net::OpenID::IndirectMessage->new($argumenty_thing); |
|---|
| 237 | |
|---|
| 238 | # Determine which protocol version the message is using. |
|---|
| 239 | # Currently this can be either 1 for 1.1 or 2 for 2.0. |
|---|
| 240 | # Expect larger numbers for other versions in future. |
|---|
| 241 | # Most callers don't really need to care about this. |
|---|
| 242 | my $version = $args->protocol_version(); |
|---|
| 243 | |
|---|
| 244 | # Get a core argument value ("openid.mode") |
|---|
| 245 | my $mode = $args->get("mode"); |
|---|
| 246 | |
|---|
| 247 | # Get an extension argument value |
|---|
| 248 | my $nickname = $args->get_ext("http://openid.net/extensions/sreg/1.1", "nickname"); |
|---|
| 249 | |
|---|
| 250 | # Get hashref of all arguments in a given namespace |
|---|
| 251 | my $sreg = $args->get_ext("http://openid.net/extensions/sreg/1.1"); |
|---|
| 252 | |
|---|
| 253 | Most of the time callers won't need to use this class directly, but will instead |
|---|
| 254 | access it through a L<Net::OpenID::Consumer> instance. |
|---|
| 255 | |
|---|