ã½ã¼ã¹ããèªãOpenID (1)
追è¨
ç¹ã«æå·ãèªè¨¼å¨ãã®èªåã®ç解ã足ããªãã£ãã®ã§ãçµæ§å çãã¾ãããï¼ï¼ï¼ï¼
ã¯ããã«
ä»æ§ã大äºã ãã©ãã£ã±ãã¨ã³ã¸ãã¢ãªã®ã§ã½ã¼ã¹å«ã§ããã^^
ããOpenIDã®è¦æ ¼ãç解ããçºã«ãå°ããã¤èªãã å
容ãã¡ã¢ã£ã¦è¡ãã¾ãã
- Net-OpenID-Consumer-1.18 - Library for consumers of OpenID identities - metacpan.org
- Net-OpenID-Server-1.09 - Library for building your own OpenID server/provider - metacpan.org
ãããã¹ãã«ãªãã¾ãã
Net::OpenID::Consumer
æåã«Consumerã®SYNOPSYSãè¦ãã¨
- new
- claimed_identity
- check_url
- user_setup_url or user_cancel or verified_identity
ã£ã¦æµãã«ãªã£ã¦ã¾ãã
ä»ã¾ã§ä»æ§ãèªãã§æ¥ãéãã ã¨èªè¨¼ããã¼ãã®ã¾ã¾ã£ã¦æãã«è¦ãã¾ãããç¾èã¯ä¸è¦ã«ãããã
ã¨ã«ããèªã¿é²ãã¦ã¿ã¾ãã
Net::OpenID::Consumer->new
ã¾ãã¯æ¸¡ãããã©ã¡ã¼ã¿ã¼ã§ãããã½ã¼ã¹ãè¦ãã¨ã
sub new { my Net::OpenID::Consumer $self = shift; $self = fields::new( $self ) unless ref $self; my %opts = @_; $self->{ua} = delete $opts{ua}; $self->args ( delete $opts{args} ); $self->cache ( delete $opts{cache} ); $self->consumer_secret ( delete $opts{consumer_secret} ); $self->required_root ( delete $opts{required_root} ); $self->{debug} = delete $opts{debug}; Carp::croak("Unknown options: " . join(", ", keys %opts)) if %opts; return $self; }
ã¨ããããã«ã
- ua
- args
- cache
- consumer_secret
- required_root
- debug
ã¨è¨ããã©ã¡ã¼ã¿ã渡ããããã§ãã
ãã£ããããã¥ã¡ã³ãã«ç®ãéãã¦ã¿ãã¨ã
- ua
- çç¥ããã¨LWP::UserAgentã ããLWPx::ParanoidAgentæ¨å¥¨ãããã¯ä»æ§ã«ãæ¸ãã¦ããã
- args
- URLãã©ã¡ã¼ã¿ã渡ãæ¹ã¯色々ã
- cache
- get($key), set($key, $value)ãªãä½ã§ãOKãCache::Fileã¨ãCache::Memcachedã§ãã
- consumer_secret
- 255byteãè¶ ããªãéµãLiveJournalのソースãæ¼ãã¨timestampã§çæããããã«ãã¼ã¿ãã¼ã¹ã§ç®¡çãã¦ã¾ãã*1return_urlã§ã®spoofingãé²ãçºã«çæãã¾ãã
- required_root
- return_to URLã®ããªãã£ã¯ã¹ããããå«ã¾ããªãå ´åã¯ä¸æ£ãªreturn_to URLã¨ãã¦æ±ãããã
- debug
- ãããã°æ å ±ã表示ããã¾ãã
ã£ã¦æãã§ãã
ä¾ãã°ã
my $csr = Net::OpenID::Consumer->new( consumer_secret => sub { String::Random->new->randregex('[a-zA-Z0-9]{32}'); }, debug => 1 );
ãããªã½ã¼ã¹ã«ãªã訳ã§ãã*2
Net::OpenID::Consumer->claimed_identity()
sub claimed_identity { my Net::OpenID::Consumer $self = shift; my $url = shift; Carp::croak("Too many parameters") if @_; # trim whitespace $url =~ s/^\s+//; $url =~ s/\s+$//; return $self->_fail("empty_url", "Empty URL") unless $url; # do basic canonicalization $url = "http://$url" if $url && $url !~ m!^\w+://!; return $self->_fail("bogus_url", "Invalid URL") unless $url =~ m!^https?://!i; # add a slash, if none exists $url .= "/" unless $url =~ m!^http://.+/!i; my $final_url; my $sem_info = $self->_find_semantic_info($url, \$final_url) or return; my $id_server = $sem_info->{"openid.server"} or return $self->_fail("no_identity_server"); return Net::OpenID::ClaimedIdentity->new( identity => $final_url, server => $id_server, consumer => $self, delegate => $sem_info->{'openid.delegate'}, ); }
ã¾ããããåããã®ã¯Net::OpenID::ClaimedIdentityã®ã¤ã³ã¹ã¿ã³ã¹ãçæããã¡ã½ããã£ã¦äºãªãã§ããã©ã
_find_semantic_info()ã«ãã£ã¦å®éã®Claimed Identifierãè¦ã«è¡ãããã§ããã
ããã«ãã¦ãååã®ããªãã¼ã·ã§ã³ã¯ããªãåæã§ããªw
_find_semantic_infoã§ããããªãé·ãã®ã§ãããæççã«è¦ãã¨ã
my $ret = { 'openid.server' => undef, 'openid.delegate' => undef, 'foaf' => undef, 'foaf.maker' => undef, 'rss' => undef, 'atom' => undef, };
ã£ã¦è¨ãHASHREFãè¿ãã¾ããã¡ãªã¿ã«ãã®ã¡ã½ãããããã¨ãã´ãªã´ãªæ¸ãã¦ã¾ãw
ä»ã®CPANã«ã¯ããç³»ã®ã¢ã¸ã¥ã¼ã«ã¯ãã¡ãã¨ç¨æããã¦ã*3ã®ã§ãããã¾ã§é å¼µã£ã¦æ¸ãå¿
è¦ã¯ç¡ãã§ãã^^ï¼
æçµçã«delegateãã¦ããã©ããã¾ã§ãã§ãã¯ãã¦ã¾ãããããã$final_urlã¨è¨ãå½¢ã§delegateãã¦ãå ´åãªãã°ãã®å ã®identifier urlãæã£ã¦ããã£ã¦ã¤ã¡ã¼ã¸ã§ãããã«openid.serverã®å¤ãåã£ã¦ããã£ã¦å½¢ã«ãªãã¾ãã
return Net::OpenID::ClaimedIdentity->new( identity => $final_url, server => $id_server, consumer => $self, delegate => $sem_info->{'openid.delegate'}, );
Net::OpenID::ClaimedIdentityã®ã¤ã³ã¹ã¿ã³ã¹çææã«ã¯çµå±foafãªã©ãªã©ã®ã»ãã³ãã£ãã¯ãã¼ã¿ã¯å ¨ç¶åæ ããã¦ã¾ãããªã
Net::OpenID::ClaimedIdentifier
Net::OpenID::ClaimedIdentifier->check_url()
ãããä»æ§ä¸ã¯ä½ã«å½ããããä¸çªã®çåãã½ã¼ã¹ã追ã£ã¦è¡ãã¾ãããã
é·ãã®ã§å°ããã¤ã
sub check_url { my Net::OpenID::ClaimedIdentity $self = shift; my (%opts) = @_; my $return_to = delete $opts{'return_to'}; my $trust_root = delete $opts{'trust_root'}; my $delayed_ret = delete $opts{'delayed_return'}; Carp::croak("Unknown options: " . join(", ", keys %opts)) if %opts; Carp::croak("Invalid/missing return_to") unless $return_to =~ m!^https?://!;
return_to, trust_root, delayed_returnããã©ã¡ã¼ã¿ã«ãªãã¾ãã
ããã¥ã¡ã³ãã¯ここãã§ããã©ã
ä¸å¿æãèªã¿ãã¦ããã¾ãããã
- return_to
- èªè¨¼æ¸ã¿ç½²åãuser_setup_urlãä¼´ã£ãIdPãã¦ã¼ã¶ã¼ããªãã¤ã¬ã¯ããããURLã®äºã
- trust_root
- ä¿¡é ¼ããURLã®rootã§return_toã¯ãã®é ä¸ã«ããurlã«ãªãããã«ããªããã°ãªããªããçç¥ããã¨return_toã¨åãå¤ã«ãªããã¯ã¤ã«ãã«ã¼ãã使ããã
- delayed_return
- ããã©ã«ãã¯falseã§ç´ã¡ã«èªè¨¼çµæãä¼´ãreturn_to URLã«ãªãã¤ã¬ã¯ããããã*4trueã«ããå ´åã¯ãcheck_urlã¯IdPãæã示ããã¦ã¼ã¶ã¼ãç´æ¥IdPã®å®éã®ãã¼ã¸ã«èªå°ãã主張ãå®å ¨ã«ç¢ºç«ããã¾ã§ããã«ç½®ãå»ãã«ãããæçµçã«return_toã§æå®ããURLã«æ»ã£ã¦ããã
ã£ã¦æãã§ããç¶ããèªã¿ã¾ãããã
my $csr = $self->{consumer}; my $ident_server = $self->{server} or Carp::croak("No identity server"); # get an assoc (or undef for dumb mode) my $assoc = Net::OpenID::Association::server_assoc($csr, $ident_server);
ãããã®æ®µéã§associateãè¡ãããã®ãåããã¾ãããã®é¨åã¯å¾è¿°ã
dumb mode*5ã ã¨undefã¨è¨ã辺ãã«æ³¨ç®ãã¦ç¶ãã
my $identity_arg = $self->{'delegate'} || $self->{'identity'}; # make a note back to ourselves that we're using a delegate if ($self->{'delegate'}) { OpenID::util::push_url_arg(\$return_to, "oic.identity", $self->{identity}); }
ãã®push_url_argã§ããConsumer.pmã®ä¸ã§å¥ã«package宣è¨ããã¦ã¾ãã
urlæååã®ãªãã¡ã¬ã³ã¹ã渡ãã¦ãkey-valueã®ãã¢ããã©ã¡ã¼ã¿ã«è¿½å ããã£ã¦è¨ãã¡ã½ããã
delegateã®æã¯oic.identityã£ã¦ãã¼ã«ãªããã§ããã*6
ãã®ããã«ãã¦æçµçã«Consumerå´ã«æ»ãã¹ãURLã§ããreturn_toã®å¤ã®æ§ç¯ãè¡ã£ã¦è¡ãã¾ãã
# add a HMAC-signed time so we can verify the return_to URL wasn't spoofed my $sig_time = time(); my $c_secret = $csr->_get_consumer_secret($sig_time); my $sig = substr(OpenID::util::hmac_sha1_hex($sig_time, $c_secret), 0, 20); OpenID::util::push_url_arg(\$return_to, "oic.time", "${sig_time}-$sig");
Consumerå´ã®ç§å¯éµã®çæãè¡ãã¾ãã
ãã®ç§å¯éµã使ã£ã¦æå»ã¨å
±ã«HMAC-SHA1ã§ç½²åãä½ãã¾ããå®éã®æéã¨ã»ããã®å¤ã§oic.timeã¨ãªã£ã¦ã¾ããã
ããã¾ã§ãreturn_toã®çæã
my $curl = $ident_server; OpenID::util::push_url_arg(\$curl, "openid.mode", ($delayed_ret ? "checkid_setup" : "checkid_immediate"), "openid.identity", $identity_arg, "openid.return_to", $return_to, ($trust_root ? ("openid.trust_root", $trust_root) : ()), ($assoc ? ("openid.assoc_handle", $assoc->handle) : ()), ); $self->{consumer}->_debug("check_url for (del=$self->{delegate}, id=$self->{identity}) = $curl"); return $curl; }
delayed_returnã®å¤ã¯checkid_setup(trueã®æ) or checkid_immediate(falseã®æ)ã£ã¦éãã«ãªãã¾ããã
ã¾ãsmartã¢ã¼ãã®ã¨ãã®ã¿openid.assoc_handleã«å¤ãå«ã¾ããããã«ãªãã£ã¦äºã§ãã
ãã®ããã«ãã¦IdPã®ã¨ã³ããã¤ã³ãã«å¯¾ãããã©ã¡ã¼ã¿ãçæããã®ãcheck_urlã¡ã½ããã®ä»äºã§ãã
Net::OpenID::Assosiation
Net::OpenID::Assosiation->server_assoc()
assosiationã®é¨åã®ã½ã¼ã¹ãçºãã¦ããã¾ãããã
é·ãã®ã§å°ããã¤ããã
sub server_assoc { my ($csr, $server) = @_; # closure to return undef (dumb consumer mode) and log why my $dumb = sub { $csr->_debug("server_assoc: dumb mode: $_[0]"); return undef; }; my $cache = $csr->cache; return $dumb->("no_cache") unless $cache;
ä»®ã«smartã¢ã¼ãã«ãã¦ãã£ã¦ãcacheç¨ã®ãªãã¸ã§ã¯ããè¨å®ããã¦ãªãã¨å¼·å¶çã«undefãè¿ã£ã¦ãã¾ãã
ãã®çç±ã¯å¾ã§è¿°ã¹ã¾ãã
# try first from cached association handle if (my $handle = $cache->get("shandle:$server")) { my $assoc = handle_assoc($csr, $server, $handle); if ($assoc && $assoc->usable) { $csr->_debug("Found association from cache (handle=$handle)"); return $assoc; } }
cacheãåå¨ãããã¤expireãéãã¦ããªããã°ãã®ã¾ã¾æ¡ç¨ãassociateã¨ãã¦å©ç¨ãã¾ãã
# make a new association my $dh = _default_dh(); my %post = ( "openid.mode" => "associate", "openid.assoc_type" => "HMAC-SHA1", "openid.session_type" => "DH-SHA1", "openid.dh_consumer_public" => OpenID::util::bi2arg($dh->pub_key), ); my $req = HTTP::Request->new(POST => $server); $req->header("Content-Type" => "application/x-www-form-urlencoded"); $req->content(join("&", map { "$_=" . OpenID::util::eurl($post{$_}) } keys %post)); $csr->_debug("Associate mode request: " . $req->content); my $ua = $csr->ua; my $res = $ua->request($req); # uh, some failure, let's go into dumb mode? return $dumb->("http_failure_no_associate") unless $res && $res->is_success;
DHå
±ééµå
±æã®çºã®ç§å¯éµãå
¬ééµã®ãã¢ãçæããIdPå´ã«å
¬ééµãéç¥ãã¾ãããã®éã¯æ®éã®POSTã¨ãªã£ã¦ãã¾ãã
dh_consumer_publicã«ã¤ãã¦ã¯ãã¤ãæ¸ãã¾ãã
my $recv_time = time(); my $content = $res->content; my %args = OpenID::util::parse_keyvalue($content); $csr->_debug("Response to associate mode: [$content] parsed = " . join(",", %args)); return $dumb->("unknown_assoc_type") unless $args{'assoc_type'} eq "HMAC-SHA1"; my $stype = $args{'session_type'}; return $dumb->("unknown_session_type") if $stype && $stype ne "DH-SHA1";
DHå ±ééµå ±æã«ä½¿ã£ã¦ããæå·åã¿ã¤ãã¨ã»ãã·ã§ã³ã®æå·åã¿ã¤ãã®ç¢ºèªãè¡ãã¾ãã
# protocol version 1.1 my $expires_in = $args{'expires_in'}; # protocol version 1.0 (DEPRECATED) if (! $expires_in) { if (my $issued = OpenID::util::w3c_to_time($args{'issued'})) { my $expiry = OpenID::util::w3c_to_time($args{'expiry'}); my $replace_after = OpenID::util::w3c_to_time($args{'replace_after'}); # seconds ahead (positive) or behind (negative) the server is $expires_in = ($replace_after || $expiry) - $issued; } } # between 1 second and 2 years return $dumb->("bogus_expires_in") unless $expires_in > 0 && $expires_in < 63072000;
æå¹æéã®ãã§ãã¯ãè¡ãã¾ããããã«å¾æ¹äºæãä¿ã£ã¦ããããã§ãã
my $ahandle = $args{'assoc_handle'}; my $secret; if ($stype ne "DH-SHA1") { $secret = OpenID::util::d64($args{'mac_key'}); } else { my $server_pub = OpenID::util::arg2bi($args{'dh_server_public'}); my $dh_sec = $dh->compute_secret($server_pub); $secret = OpenID::util::d64($args{'enc_mac_key'}) ^ sha1(OpenID::util::bi2bytes($dh_sec)); } return $dumb->("secret_not_20_bytes") unless length($secret) == 20;
æå·åå½¢å¼ãDH-SHA1ã§ç¡ãå ´åã¯ãBase64ãã³ã¼ããè¡ã£ã¦å
±ééµãåå¾ãã
DH-SHA1ã®å ´åã¯ãIdPå´ã®å
¬ééµãç¨ãã¦å
±ééµãåå¾ãã¾ãã
ããã¯å¼·ãDH-SHA1ã§æå·åããäºãæã¾ãã¦ãã®ã§ãDH-SHA1ã§ã®æå·åã使ç¨ããã¹ãã§ãããã
my %assoc = ( handle => $ahandle, server => $server, secret => $secret, type => $args{'assoc_type'}, expiry => $recv_time + $expires_in, ); my $assoc = Net::OpenID::Association->new( %assoc ); return $dumb->("assoc_undef") unless $assoc; $cache->set("hassoc:$server:$ahandle", Storable::freeze(\%assoc)); $cache->set("shandle:$server", $ahandle); # now we test that the cache object given to us actually works. if it # doesn't, it'll also fail later, making the verify fail, so let's # go into stateless (dumb mode) earlier if we can detect this. $cache->get("shandle:$server") or return $dumb->("cache_broken"); return $assoc; }
- assoc_handle
- server
- assoc_type(HMAC_SHA1)
- expire
ãã²ã¨ããã¾ãã¨ãã¦ãConsumerå´ã§ãã£ãã·ã¥åãã¦ãã¾ãç¹å®ã®IdPã¨ã©ã®ãããªassoc_handleã§å
±ééµãå
±æãããã®å¯¾å¿ããã£ãã·ã¥åãã¾ãã
æå¾ã«ã¤ã³ã¹ã¿ã³ã¹çæãã¦ã念å
¥ãã«ãã£ãã·ã¥ã®ãã§ãã¯ãè¡ã£ã¦çµäºã¨ãªãã¾ãã
ã¾ã¨ã
ã½ã¼ã¹å«ã£ã¦äºã ãããä»æ§ãããã£ã½ã©åãããããç½ ã
- ConsumerãEnd Userã®claimed identityã®åå¾ãè¡ã
- ConsumerãIdPã¨assosiationãè¡ã
- ConsumerãEnd Userã«check_urlãç¨æãã
ã¨è¨ãæç¶ãã¾ã§ç°¡åã«è¿½ãã¾ããã
次åã¯ãã®ç¶ãããããã«è¿½ã£ã¦è¡ããã¨æãã¾ãã
*2:consumer_secretã¯è²§å¼±éããªã®ã§çä¼¼ããªãããã«ï¼w
*3:http://search.cpan.org/search?query=WWW%3A%3ABlog%3A%3AMetadata&mode=dist 辺ã
*5:å ±ééµå ±æãè¡ã£ã¦ç¡ãã¢ã¼ã
*6:ä»æ§ã«ãªãæ°ããããã©ããã