lib/redfish_client/root.rb in redfish_client-0.1.0 vs lib/redfish_client/root.rb in redfish_client-0.2.0

- old
+ new

@@ -1,55 +1,95 @@ # frozen_string_literal: true +require "base64" require "redfish_client/resource" module RedfishClient # Root resource represents toplevel entry point into Redfish service data. # Its main purpose is to provide authentication support for the API. class Root < Resource # AuthError is raised if the user session cannot be created. class AuthError < StandardError; end - # Token authentication header. - AUTH_HEADER = "X-Auth-Token" + # Basic and token authentication headers. + BASIC_AUTH_HEADER = "Authorization" + TOKEN_AUTH_HEADER = "X-Auth-Token" # Authenticate against the service. # # Calling this method will try to create new session on the service using - # provided credentials. If the session creation fails, {AuthError} will be - # raised. + # provided credentials. If the session creation fails, basic + # authentication will be attempted. If basic authentication fails, + # {AuthError} will be raised. # # @param username [String] username # @param password [String] password # @raise [AuthError] if user session could not be created def login(username, password) - r = self.Links.Sessions.post( - payload: { "UserName" => username, "Password" => password } - ) - raise AuthError unless r.status == 201 - - logout - rdata = r.data - @connector.add_headers(AUTH_HEADER => rdata[:headers][AUTH_HEADER]) - @session = Resource.new(@connector, content: JSON.parse(rdata[:body])) + # Since session auth is more secure, we try it first and use basic auth + # only if session auth is not available. + if session_login_available? + session_login(username, password) + else + basic_login(username, password) + end end # Sign out of the service. # # If the session could not be deleted, {AuthError} will be raised. def logout - return unless @session - r = @session.delete - raise AuthError unless r.status == 204 - @session = nil - @connector.remove_headers([AUTH_HEADER]) + session_logout + basic_logout end # Find Redfish service object by OData ID field. # # @param oid [String] Odata id of the resource # @return [Resource] new resource def find(oid) Resource.new(@connector, oid: oid) + end + + private + + def session_login_available? + !@content.dig("Links", "Sessions").nil? + end + + def session_login(username, password) + r = self.Links.Sessions.post( + payload: { "UserName" => username, "Password" => password } + ) + raise AuthError, "Invalid credentials" unless r.status == 201 + + session_logout + + payload = r.data[:headers][TOKEN_AUTH_HEADER] + @connector.add_headers(TOKEN_AUTH_HEADER => payload) + @session = Resource.new(@connector, content: JSON.parse(r.data[:body])) + end + + def session_logout + return unless @session + r = @session.delete + raise AuthError unless r.status == 204 + @session = nil + @connector.remove_headers([TOKEN_AUTH_HEADER]) + end + + def auth_test_path + @content.values.map { |v| v["@odata.id"] }.compact.first + end + + def basic_login(username, password) + payload = Base64.encode64("#{username}:#{password}").strip + @connector.add_headers(BASIC_AUTH_HEADER => "Basic #{payload}") + r = @connector.get(auth_test_path) + raise AuthError, "Invalid credentials" unless r.status == 200 + end + + def basic_logout + @connector.remove_headers([BASIC_AUTH_HEADER]) end end end