# This module covers Brightbox's partial implementation of OAuth 2.0 # and enables fog clients to implement several authentictication strategies # # @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10 # module Fog::Brightbox::OAuth2 # This builds the simplest form of requesting an access token # based on the arguments passed in # # @param [Fog::Connection] connection # @param [CredentialSet] credentials # # @return [Excon::Response] def request_access_token(connection, credentials) token_strategy = credentials.best_grant_strategy header_content = "#{credentials.client_id}:#{credentials.client_secret}" encoded_credentials = Base64.encode64(header_content).chomp connection.request({ :path => "/token", :expects => 200, :headers => { 'Authorization' => "Basic #{encoded_credentials}", 'Content-Type' => 'application/json' }, :method => 'POST', :body => Fog::JSON.encode(token_strategy.authorization_body_data) }) end # Encapsulates credentials required to request access tokens from the # Brightbox authorisation servers # # @todo Interface to update certain credentials (after password change) # class CredentialSet attr_reader :client_id, :client_secret, :username, :password attr_reader :access_token, :refresh_token, :expires_in # # @param [String] client_id # @param [String] client_secret # @param [Hash] options # @option options [String] :username # @option options [String] :password # def initialize(client_id, client_secret, options = {}) @client_id = client_id @client_secret = client_secret @username = options[:username] @password = options[:password] @access_token = options[:access_token] @refresh_token = options[:refresh_token] @expires_in = options[:expires_in] end # Returns true if user details are available # @return [Boolean] def user_details? !!(@username && @password) end # Is an access token available for these credentials? def access_token? !!@access_token end # Is a refresh token available for these credentials? def refresh_token? !!@refresh_token end # Updates the credentials with newer tokens def update_tokens(access_token, refresh_token = nil, expires_in = nil) @access_token = access_token @refresh_token = refresh_token @expires_in = expires_in end # Based on available credentials returns the best strategy # # @todo Add a means to dictate which should or shouldn't be used # def best_grant_strategy if refresh_token? RefreshTokenStrategy.new(self) elsif user_details? UserCredentialsStrategy.new(self) else ClientCredentialsStrategy.new(self) end end end # This strategy class is the basis for OAuth2 grant types # # @abstract Need to implement {#authorization_body_data} to return a # Hash matching the expected parameter form for the OAuth request # # @todo Strategies should be able to validate if credentials are suitable # so just client credentials cannot be used with user strategies # class GrantTypeStrategy def initialize(credentials) @credentials = credentials end def authorization_body_data raise "Not implemented" end end # This implements client based authentication/authorization # based on the existing trust relationship using the `none` # grant type. # class ClientCredentialsStrategy < GrantTypeStrategy def authorization_body_data { "grant_type" => "none", "client_id" => @credentials.client_id } end end # This passes user details through so the returned token # carries the privileges of the user not account limited # by the client # class UserCredentialsStrategy < GrantTypeStrategy def authorization_body_data { "grant_type" => "password", "client_id" => @credentials.client_id, "username" => @credentials.username, "password" => @credentials.password } end end # This strategy attempts to use a refresh_token gained during an earlier # request to reuse the credentials given originally # class RefreshTokenStrategy < GrantTypeStrategy def authorization_body_data { "grant_type" => "refresh_token", "client_id" => @credentials.client_id, "refresh_token" => @credentials.refresh_token } end end private # This updates the current credentials if passed a valid response # # @param [CredentialSet] credentials Credentials to update # @param [Excon::Response] response Response object to parse value from # def update_credentials_from_response(credentials, response) response_data = Fog::JSON.decode(response.body) credentials.update_tokens(response_data["access_token"], response_data["refresh_token"], response_data["expires_in"]) end end