lloydmeta/push_to_devices

View on GitHub
lib/api_auth.rb

Summary

Maintainability
A
1 hr
Test Coverage
require 'openssl'

module ApiAuth

    API_TIMESTAMP_TOLERANCE = 10.minutes

    def api_credentials
      @api_credentials || extract_api_credentials
    end

    def extract_api_credentials
      @api_credentials = {}
      @api_credentials[:server_client_id] = request.env["HTTP_SERVER_CLIENT_ID"] || request.env["server-client-id"]
      @api_credentials[:mobile_client_id] = request.env["HTTP_MOBILE_CLIENT_ID"] || request.env["mobile-client-id"]
      @api_credentials[:client_sig] = request.env["HTTP_CLIENT_SIG"] || request.env["client-sig"]
      @api_credentials[:timestamp] = request.env["HTTP_TIMESTAMP"] || request.env["timestamp"]
      @api_credentials
    end

    def api_current_user?(user)
      user == api_current_user
    end

    def api_current_user=(user)
      @api_current_user = user
    end

    def api_current_user
      if api_signed_in?
       @api_current_user
     else
        api_authenticate
        @api_current_user
      end
    end

    def api_authenticate
      if api_credentials[:server_client_id]
        client_id = api_credentials[:server_client_id]
        mobile_client = false
      else
        client_id = api_credentials[:mobile_client_id]
        mobile_client = true
      end
      timestamp = api_credentials[:timestamp]
      client_sig = api_credentials[:client_sig]
      # sanity check
      if [client_id, timestamp, client_sig].any?{|p| p.nil?}
        api_deny_access("api-auth-err-ensure-client-id-timestamp-client-sig-all-sent")
      elsif ! timestamp_valid?(timestamp)
        api_deny_access("api-auth-err-timestamp-invalid")
      elsif service = api_credentials_valid?(client_id, timestamp, client_sig, mobile_client)
        self.api_current_user = service
        service
      else
        api_deny_access
      end
    end

    def api_deny_access(message="api-auth-err-authentication-failed")
      Padrino::logger.info "API Auth failed: #{message}"
      content_type :json
      halt 403,  {error: message}.to_json
    end

    def api_signed_in?
      !@api_current_user.nil?
    end

    # simple method to find a service based on client_id
    def retrieve_by_client_id(client_id, mobile_client)
      if mobile_client
        client_credentials = Service.where(mobile_client_id: client_id).first
      else
        client_credentials = Service.where(server_client_id: client_id).first
      end
    end

    private

    def securerandom_string(n = 23)
      SecureRandom.urlsafe_base64(n, true)
    end

    # Checks to see if the credentials given are valid
    # Returns false if not valid
    # Returns the service if everything checks out
    def api_credentials_valid?(client_id, timestamp, given_signature, mobile_client)
      # Fetch the service and calculate the HMAC signature and compare
      # With what the client sent along
      service = retrieve_by_client_id(client_id, mobile_client)
      if service.present?
        if mobile_client
          client_secret = service.mobile_client_secret
        else
          client_secret = service.server_client_secret
        end
        calculated_signature = OpenSSL::HMAC.hexdigest 'sha1', client_secret, timestamp
        if calculated_signature == given_signature
          service
        else
          false
        end
      else
        false
      end
    end

    def timestamp_valid?(timestamp)
      (API_TIMESTAMP_TOLERANCE.ago.to_i .. API_TIMESTAMP_TOLERANCE.from_now.to_i).cover?(timestamp.to_i)
    end

end