class Contacts::WindowsLive

Attributes

delegation_token[RW]
token_expires_at[RW]

Public Class Methods

new(options={}) click to toggle source
# File lib/contacts/windows_live.rb, line 53
def initialize(options={})
  @token_expires_at = nil
  @location_id = nil
  @delegation_token = nil
end

Public Instance Methods

authentication_url(target=self.return_url, options={}) click to toggle source
# File lib/contacts/windows_live.rb, line 73
def authentication_url(target=self.return_url, options={})
  if force_origin
    context = target
    target = force_origin + URI.parse(target).path
  end

  url = "https://consent.live.com/Delegation.aspx"
  query = {
    'ps' => 'Contacts.Invite',
    'ru' => target,
    'pl' => privacy_policy_url,
    'app' => app_verifier,
  }
  query['appctx'] = context if context
  "#{url}?#{params_to_query(query)}"
end
authorize(params) click to toggle source
# File lib/contacts/windows_live.rb, line 95
def authorize(params)
  consent_token_data = params['ConsentToken'] or
    raise Error, "no ConsentToken from Windows Live"
  eact = backwards_query_to_params(consent_token_data)['eact'] or
    raise Error, "missing eact from Windows Live"
  query = decode_eact(eact)
  consent_authentic?(query) or
    raise Error, "inauthentic Windows Live consent"
  params = query_to_params(query)
  @token_expires_at = Time.at(params['exp'].to_i)
  @location_id = params['lid']
  @delegation_token = params['delt']
  true
rescue Error => error
  @error = error.message
  false
end
contacts(options={}) click to toggle source
# File lib/contacts/windows_live.rb, line 113
def contacts(options={})
  return nil if @delegation_token.nil? || @token_expires_at < Time.now
  # TODO: Handle expired token.
  xml = request_contacts
  parse_xml(xml)
end
forced_redirect_url(params) click to toggle source
# File lib/contacts/windows_live.rb, line 90
def forced_redirect_url(params)
  target_origin = params['appctx'] and
    "#{target_origin}?#{params_to_query(params)}"
end
initialize_serialized(data) click to toggle source
# File lib/contacts/windows_live.rb, line 59
def initialize_serialized(data)
  @token_expires_at = Time.at(data['token_expires_at'].to_i)
  @location_id = data['location_id']
  @delegation_token = data['delegation_token']
end
serializable_data() click to toggle source
# File lib/contacts/windows_live.rb, line 65
def serializable_data
  data = {}
  data['token_expires_at'] = @token_expires_at.to_i if @token_expires_at
  data['location_id'] = @location_id if @location_id
  data['delegation_token'] = @delegation_token if @delegation_token
  data
end

Private Instance Methods

app_verifier() click to toggle source
# File lib/contacts/windows_live.rb, line 130
def app_verifier
  token = params_to_query({
    'appid' => application_id,
    'ts' => Time.now.to_i,
  })
  token << "&sig=#{CGI.escape(Base64.encode64(sign(token)))}"
end
backwards_query_to_params(data) click to toggle source

Like query_to_params, but do the unescaping before the splitting on '&' and '=', like Microsoft does it.

# File lib/contacts/windows_live.rb, line 162
def backwards_query_to_params(data)
  params={}
  CGI.unescape(data).split(/&/).each do |pair|
    key, value = *pair.split(/=/)
    params[key] = value ? value : ''
  end
  params
end
decode_eact(eact) click to toggle source
# File lib/contacts/windows_live.rb, line 142
def decode_eact(eact)
  token = Base64.decode64(CGI.unescape(eact))
  iv, crypted = token[0...16], token[16..-1]
  cipher = OpenSSL::Cipher::AES128.new("CBC")
  cipher.decrypt
  cipher.iv = iv
  cipher.key = encryption_key
  cipher.update(crypted) + cipher.final
end
encryption_key() click to toggle source
# File lib/contacts/windows_live.rb, line 126
def encryption_key
  OpenSSL::Digest::SHA256.digest("ENCRYPTION#{secret_key}")[0...16]
end
parse_xml(xml) click to toggle source
# File lib/contacts/windows_live.rb, line 179
def parse_xml(xml)
  document = Nokogiri::XML(xml)
  document.search('/LiveContacts/Contacts/Contact').map do |contact|
    email = contact.at('PreferredEmail').inner_text.strip
    names = []
    element = contact.at('Profiles/Personal/FirstName') and
      names << element.inner_text.strip
    element = contact.at('Profiles/Personal/LastName') and
      names << element.inner_text.strip
    Contact.new(email,names.join(' '))
  end
end
request_contacts() click to toggle source
# File lib/contacts/windows_live.rb, line 171
def request_contacts
  http = Net::HTTP.new('livecontacts.services.live.com', 443)
  http.use_ssl = true
  url = "/users/@L@#{@location_id}/rest/invitationsbyemail"
  authorization = "DelegatedToken dt=\"#{@delegation_token}\""
  http.get(url, {"Authorization" => authorization}).body
end
sign(token) click to toggle source
# File lib/contacts/windows_live.rb, line 138
def sign(token)
  OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, signature_key, token)
end
signature_key() click to toggle source
# File lib/contacts/windows_live.rb, line 122
def signature_key
  OpenSSL::Digest::SHA256.digest("SIGNATURE#{secret_key}")[0...16]
end