l'essentiel est invisible pour les yeux

Monday, March 24, 2008

[ruby] Why does StoreRequest of OpenID Attribute Exchange (AX) attract our heart? (and DEMO of AX StoreRequest with ruby-openid)

AX StoreRequest message is defined in OpenID Attribute Exchange 1.0. It can store personal identity information to the OpenID Provider from RP. It's exciting specification because we use many services on WWW, and input variety of many personal identity in sites. We can aggregate our personal identity automatically with AX StoreRequest message! Additionally, updated personal identity is synced AX's update_uri property across the all services!

Demo that sent AX StoreRequest message, and dispatch AX StoreResponse message with ruby-openid as follows.

At first, we need to extend ruby-openid to handle AX StoreRequest and StoreResponse usefully.
openid_ax_ext.rb


require 'rubygems'
require 'openid/extension'
require 'openid/message'

module OpenID
module AX
class StoreRequest
# Extract a StoreRequest from an OpenID message
def self.from_openid_request(oid_req)
message = oid_req.message
ax_args = message.get_args(NS_URI)
return nil if ax_args.empty?
req = new
req.parse_extension_args(ax_args)
req
end
end

class StoreResponse
def self.from_success_response(success_response)
resp = nil
ax_args = success_response.message.get_args(NS_URI)
resp = ax_args.key?('error') ? new(false, ax_args['error']) : new
end
end
end
end


app/controller/consumer_controller.rb

# in ConsumerController#start
# AX StoreRequest message
if params[:use_ax_store]
ax_store_req = OpenID::AX::StoreRequest.new
ax_store_req.set_values('http://axschema.org/contact/email', %w(rakuto@example.com rakuto@example2.com))
ax_store_req.set_values('http://axschema.org/birthDate', %w(1985-03-12))
ax_store_req.set_values('http://axschema.org/contact/IM/Skype', %w(rfurutani))
oidreq.add_extension(ax_store_req)
oidreq.return_to_args['did_ax_store'] = 'y'
end


Next, OP handles the request included StoreRequest message.
app/controller/server_controller.rb

# in ServerController#start
# Check the AX StoreRequest
@ax_store_req = OpenID::AX::StoreRequest.from_openid_request(oidreq)


And OP must save personal identity sent from RP.

# Extract Personal Identity from request and save it.
# Check the StoreaRequest for AX
ax_store_resp = nil
ax_store_req = OpenID::AX::StoreRequest.from_openid_request(oidreq)
if ax_store_req
begin
# I think all user's attribute should be saved, but table structure of RDBMS VERY statically.
# So we need to respond StoreResponse mode is 'store_response_failure'.
# In a nut shell, we may use XMLDB by now!
ax_store_req.data.each do |type_uri, values|
case type_uri
when 'http://axschema.org/email'
# XXX: This is dummy
user.emails.concat(values)
when 'http://axschema.org/birthDate'
# XXX: This is dummy
user.birth_date = Date.parse(values.first)
when 'http://axschema.org/contact/IM/Skype'
# XXX: This is dummy
# Just one
user.skype_id = values.first
else
ax_store_resp = OpenID::AX::StoreResponse.new(false, "#{type_uri} isn't supported Type URI in this service.")
end
end
user.save
rescue Error => e
ax_store_resp = OpenID::AX::StoreResponse.new(false, e.message)
end
end
oidresp.add_extension(ax_store_resp || OpenID::AX::StoreResponse.new)


This is the sample view for AX StoreRequest added the checkbox enable it.

<% if flash[:ax_store_results] %>
<div class='error'>
<%= flash[:ax_store_results] %>
</div>
<% elsif params[:did_ax_store] -%>
<div class='alert'>
Your Personal identity has saved.
</div>
<% end %>
<div id="verify-form">
<form method="get" accept-charset="UTF-8"
action='<%= url_for :action => 'start' %>'>
Identifier:
<input type="text" class="openid" name="openid_identifier" />
<input type="submit" value="Verify" /><br />
<input type="checkbox" name="immediate" id="immediate" /><label for="immediate">Use immediate mode</label><br/>
<input type="checkbox" name="use_sreg" id="use_sreg" /><label for="use_sreg">Request registration data</label><br/>
<input type="checkbox" name="use_pape" id="use_pape" /><label for="use_pape">Request phishing-resistent auth policy (PAPE)</label><br/>
<input type="checkbox" name="force_post" id="force_post" /><label for="force_post">Force the transaction to use POST by adding 2K of extra d
ata</label><br/>
<input type="checkbox" name="use_ax" id="use_ax" /><label for="use_ax">Request registration data with AX</label><br/>
<input type="checkbox" name="use_ax_store" id="use_ax_store" /><label for="use_ax_store">Request store attributes with AX</label>
</form>
</div>


Confirmation of stored personal identities.
app/views/server/decide.rhtml

<% # XXX: Part of axschema -%>
<% axschema_map = {
'http://axschema.org/birthDate' => 'Birthdate',
'http://axschema.org/contact/email' => 'Email',
'http://axschema.org/contact/IM/Skype' => 'Skype'
} -%>
<% # Is StoreRequest included in request? -%>
<% if @store_req -%>
<div class="attention">
<p>And do you accept following data sent from RP?</p>
<dl>
<% @store_req.data.each do |k,v| -%>
<dt><%= axschema_map[k] -%>:</dt>
<dd><%= h v.join(', ') -%></dd>
<% end -%>
</dl>
</div>
<% end -%>


Finally, request is coming back to RP, so We need to handle success response.
app/controllers/consumer_controller.rb

# in ConsumerController#complete
# for AX store
if params[:did_ax_store]
ax_store_resp = OpenID::AX::StoreResponse.from_success_response(oidresp)
flash[:ax_store_results] = ax_store_resp.error_message unless ax_store_resp.succeeded?
end


Screenshot
These screenshot are capture output of above code.


Confirmation of stored personal identities.


Conclusion
This future is pretty interesting, but it isn't suppoted even myOpenID now. In the future, I think many OP supports AX StoreRequest. I wish!