module Oauth2
  class AuthenticationController < ApplicationController
    skip_before_filter :verify_authenticity_token

    around_filter :oauth2_error_handler

    before_filter :validate_oauth2_type!
    before_filter :validate_oauth2_client_id!
    before_filter :validate_oauth2_redirect_url!

    before_filter :authenticate_user!, :only => :authorize
    before_filter :validate_oauth2_client_secret!, :only => :access_token

    def authorize
      args = params.slice(:client_id, :redirect_url)
      args[:user_id] = current_user.uuid
      token = Oauth2Token.create!(args)
      uri_params = { :code => token.code }
      uri_params[:state] = params[:state] if params.has_key?(:state)
      uri = build_uri(params[:redirect_url], uri_params)
      redirect_to(uri)
    end

    def access_token
      token = Oauth2Token.find!(params)
      render :text => { :access_token => token.token }.to_uri, :type => :url_encoded_form, :status => :ok
    end

    protected

    # Ensures that the type of flow is supported
    def validate_oauth2_type!
      type = params[:type]
      raise Vidibus::Oauth2Server::MissingTypeError if type.blank?
      raise Vidibus::Oauth2Server::UnsupportedTypeError unless Vidibus::Oauth2Server::FLOWS.include?(type)
    end

    # Ensures that given client id is valid
    def validate_oauth2_client_id!
      raise Vidibus::Oauth2Server::MissingClientIdError if params[:client_id].blank?
      @oauth2_client = oauth2_client(params[:client_id])
      raise Vidibus::Oauth2Server::InvalidClientIdError unless @oauth2_client
    end

    # Ensures that redirect_url is valid for given client.
    def validate_oauth2_redirect_url!
      redirect_url = params[:redirect_url]
      raise Vidibus::Oauth2Server::MissingRedirectUrlError if redirect_url.blank?
      raise Vidibus::Oauth2Server::MalformedRedirectUrlError unless valid_uri?(redirect_url)
      unless redirect_url.match(/^https?:\/\/([a-z0-9]+\.)?#{@oauth2_client.domain}/) # allow subdomains but ensure host of client application
        raise Vidibus::Oauth2Server::InvalidRedirectUrlError
      end
    end

    # Ensures that given client_secret is valid for given client.
    def validate_oauth2_client_secret!
      raise Vidibus::Oauth2Server::InvalidClientSecretError unless @oauth2_client.valid_oauth2_secret?(params[:client_secret])
    end

    # Returns error message for given exception.
    def oauth2_error_handler
      begin
        yield
      rescue Vidibus::Oauth2Server::MissingTypeError
        error = "missing_type"
      rescue Vidibus::Oauth2Server::UnsupportedTypeError
        error = "unsupported_type"
      rescue Vidibus::Oauth2Server::MissingClientIdError
        error = "missing_client_id"
      rescue Vidibus::Oauth2Server::InvalidClientIdError
        error = "invalid_client_id"
      rescue Vidibus::Oauth2Server::InvalidClientSecretError
        error = "invalid_client_secret"
      rescue Vidibus::Oauth2Server::MissingRedirectUrlError
        error = "missing_redirect_url"
      rescue Vidibus::Oauth2Server::MalformedRedirectUrlError
        error = "malformed_redirect_url"
      rescue Vidibus::Oauth2Server::InvalidRedirectUrlError
        error = "invalid_redirect_url"
      rescue Vidibus::Oauth2Server::MissingCodeError
        error = "missing_code"
      rescue Vidibus::Oauth2Server::InvalidCodeError
        error = "invalid_code"
      rescue Vidibus::Oauth2Server::ExpiredCodeError
        error = "expired_code"
      rescue Vidibus::Oauth2Server::InvalidTokenError
        error = "invalid_token"
      rescue Vidibus::Oauth2Server::ExpiredTokenError
        error = "expired_token"
      ensure
        if error
          status ||= :bad_request
          render :text => I18n.t("oauth2_server.errors.#{error}"), :status => status
        end
      end

      # Autorization error?
      # :status => :unauthorized # The response MUST include a WWW-Authenticate header field (section 14.47) containing a challenge applicable to the requested resource.
      # :status => :forbidden # Maybe better?
    end
  end
end