diff --git a/.env b/.env index 4aec14e7..88359ed6 100644 --- a/.env +++ b/.env @@ -35,7 +35,7 @@ ROLLBAR_POST_CLIENT_ITEM_TOKEN="" GITLAB_APP_ID= GITLAB_APP_SECRET= GITLAB_REDIRECT_URI= -GITLAB_API_ENDPOINT= +GITLAB_API_ENDPOINT="https://gitlab.com/api/v4" GITLAB_WEBHOOK_SECRET= GITLAB_DEPLOYQA_BOT_ID= GITLAB_DEPLOYQA_BOT_TOKEN= diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 3ee3a00a..e9a6ba7a 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -7,14 +7,14 @@ class SessionsController < ApplicationController def show; end def create - result = user_from_omniauth + result = current_user ? connect_user : user_from_omniauth if result.error? flash.notice = result.errors.join("/n") return redirect_to sessions_path end - setup_session(request.session, result.object) + setup_session(result.object) redirect_to projects_path end @@ -25,8 +25,14 @@ def destroy protected - def setup_session(session, user) - ::Auth::SessionHandler.new(session).set!(user_id: user.id, provider: auth_info_presenter.provider) + def setup_session(user) + return if current_user + + ::Auth::SessionHandler.new(request.session).set!(user_id: user.id, provider: auth_info_presenter.provider) + end + + def connect_user + Auth::AnotherServiceConnection.new(auth_info_presenter, current_user).call end def user_from_omniauth diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb new file mode 100644 index 00000000..9ca41239 --- /dev/null +++ b/app/controllers/users_controller.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class UsersController < ApplicationController + def show + @user = find_user + @github_auth = @user.user_references.find { |reference| reference.auth_provider == OmniauthConstants::GITHUB } + @gitlab_auth = @user.user_references.find { |reference| reference.auth_provider == OmniauthConstants::GITLAB } + end + + private + + def find_user + authorize User.includes(user_references: :auth_info).find(params[:id]), :show?, policy_class: UserPolicy + end +end diff --git a/app/models/user_reference.rb b/app/models/user_reference.rb index b3d53b9f..9213fdde 100644 --- a/app/models/user_reference.rb +++ b/app/models/user_reference.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class UserReference < ApplicationRecord - belongs_to :user, dependent: :destroy, required: false + belongs_to :user, required: false has_one :auth_info, dependent: :destroy validates :full_name, :auth_uid, :auth_provider, presence: true diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb new file mode 100644 index 00000000..1c3332aa --- /dev/null +++ b/app/policies/user_policy.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class UserPolicy < ApplicationPolicy + def show? + return true if user.system_role == UserConstants::SystemRoles::ADMIN + + user.actual_user == record + end +end diff --git a/app/services/auth/accessibility_check.rb b/app/services/auth/accessibility_check.rb index 2777980c..cc33af58 100644 --- a/app/services/auth/accessibility_check.rb +++ b/app/services/auth/accessibility_check.rb @@ -7,16 +7,33 @@ def initialize(user_reference, email) @email = email end - def call + def ensure_user_doesnt_exists! raise ::Auth::NotPermittedError, "User already exists" if @user_reference.blank? && auth_info_email_occupied? + end + def ensure_reference_is_not_secondary! return if @user_reference.blank? return if @user_reference.auth_info.blank? return if @user_reference.auth_info.primary? - raise ::Auth::NotPermittedError, "Please login with another provider" + raise ::Auth::NotPermittedError, "You've already registered in another provider with the same email" + end + + def ensure_reference_is_not_connected! + return if @user_reference.blank? + return if @user_reference.auth_info.blank? && @user_reference.user_id.blank? + + raise ::Auth::NotPermittedError, "New account is already registered in system" end + def ensure_email_is_the_same!(current_user) + return if current_user.auth_info.email == @email + + raise ::Auth::NotPermittedError, "Your email '#{@email}' doesn't match with your account's email '#{current_user.auth_info.email}'" + end + + private + def auth_info_email_occupied? AuthInfo.find_by(email: @email).present? end diff --git a/app/services/auth/another_service_connection.rb b/app/services/auth/another_service_connection.rb new file mode 100644 index 00000000..a509886f --- /dev/null +++ b/app/services/auth/another_service_connection.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module Auth + class AnotherServiceConnection + def initialize(auth_info_presenter, current_user) + @auth_info_presenter = auth_info_presenter + @current_user = current_user + end + + def call + user_reference = UserReference.find_by(auth_uid: @auth_info_presenter.uid, auth_provider: @auth_info_presenter.provider) + ensure_user_is_ready!(user_reference) + + ActiveRecord::Base.transaction do + user_reference = user_reference.present? ? update_reference(user_reference) : create_reference + create_auth_info(user_reference) + end + + ReturnValue.ok(@current_user.actual_user) + rescue ::Auth::NotPermittedError => error + ReturnValue.error(errors: error.message) + end + + private + + def ensure_user_is_ready!(user_reference) + checker = Auth::AccessibilityCheck.new(user_reference, @auth_info_presenter.email) + checker.ensure_reference_is_not_connected! + checker.ensure_email_is_the_same!(@current_user) + end + + def update_reference(user_reference) + user_reference.update!(user: @current_user.actual_user) + user_reference + end + + def create_reference + UserReference.create!( + user: @current_user.actual_user, + auth_uid: @auth_info_presenter.uid, + auth_provider: @auth_info_presenter.provider, + full_name: @auth_info_presenter.full_name + ) + end + + def create_auth_info(user_reference) + auth_info_params = Auth::AuthInfoParamsBuilder.new(@auth_info_presenter, user_reference).call + AuthInfo.create!(auth_info_params) + end + end +end diff --git a/app/services/auth/auth_info_params_builder.rb b/app/services/auth/auth_info_params_builder.rb index c21434e5..d3bbff16 100644 --- a/app/services/auth/auth_info_params_builder.rb +++ b/app/services/auth/auth_info_params_builder.rb @@ -24,8 +24,9 @@ def call def load_email(params) return if params[:email].present? + # TODO: why do we need USER_API_CLIENT? GitLab always have an email and there is no :email method in ProviderAPI::Gitlab::UserClient api_client = USER_API_CLIENT.fetch(@omniauth_info_presenter.provider).new(@omniauth_info_presenter.token) - email = api_client.emails.find { |email_info| email_info[:primary] }.fetch(:email) + email = api_client.emails.find { |email_info| email_info[:primary] }[:email] params[:email] = email end end diff --git a/app/services/auth/user_authenticator.rb b/app/services/auth/user_authenticator.rb index 97c48c75..273d7ecc 100644 --- a/app/services/auth/user_authenticator.rb +++ b/app/services/auth/user_authenticator.rb @@ -4,10 +4,12 @@ module Auth class UserAuthenticator def initialize(auth_info_presenter) @auth_info_presenter = auth_info_presenter + @checker = Auth::AccessibilityCheck.new(user_reference, @auth_info_presenter.email) end def call - Auth::AccessibilityCheck.new(user_reference, @auth_info_presenter.email).call + @checker.ensure_user_doesnt_exists! + @checker.ensure_reference_is_not_secondary! if user_reference.blank? ::Auth::UserCreator.new(@auth_info_presenter).call diff --git a/app/services/auth/user_wrapper.rb b/app/services/auth/user_wrapper.rb index 0367b026..7c24ba78 100644 --- a/app/services/auth/user_wrapper.rb +++ b/app/services/auth/user_wrapper.rb @@ -24,5 +24,9 @@ def email def token auth_info.token end + + def actual_user + @user + end end end diff --git a/app/views/layouts/_header.html.slim b/app/views/layouts/_header.html.slim index 415e32ae..2d3299a5 100644 --- a/app/views/layouts/_header.html.slim +++ b/app/views/layouts/_header.html.slim @@ -10,4 +10,5 @@ nav.navbar.navbar-expand-lg.navbar-light.bg-light a.nav-link.dropdown-toggle href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" = current_user.full_name .dropdown-menu aria-labelledby="navbarDropdown" + = link_to "User preferences", user_path(current_user.actual_user), class: "dropdown-item" = link_to "Log out", sessions_path, method: :delete, class: "dropdown-item" diff --git a/app/views/users/show.html.slim b/app/views/users/show.html.slim new file mode 100644 index 00000000..41be1903 --- /dev/null +++ b/app/views/users/show.html.slim @@ -0,0 +1,23 @@ +.container + = render partial: "shared/breadcrumb", locals: {\ + text_link_hash: {\ + "User" => nil\ + }, + class: "mt-4" } + + + .d-flex.justify-content-between.mt-4 + h1 = current_user.full_name + .mt-4 + p + = image_pack_tag("media/images/logos/github-logo.svg", size: "32x32", class: "mr-2") + - if @github_auth.present? + span.text-success Github integration connected! + - else + = link_to "Click to connect Github", omniauth_path("github"), method: :post + p + = image_pack_tag("media/images/logos/gitlab-logo.svg", size: "32x32", class: "mr-2") + - if @gitlab_auth.present? + span.text-success Gitlab integration connected! + - else + = link_to "Click to connect Gitlab", omniauth_path("gitlab", some_data: "123"), method: :post diff --git a/config/routes.rb b/config/routes.rb index 6e63376f..603e657f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -12,13 +12,14 @@ get "/auth/slack/callback", to: "slack/authentications#create" get "/auth/failure", to: "slack/authentications#show" get "/auth/:provider/callback", to: "sessions#create" - get "/auth/:provider", to: "sessions#show", as: "omniauth" + post "/auth/:provider", to: "sessions#show", as: "omniauth" constraints Routes::LoggedUserConstraint.new(SidekiqPolicy) do mount Sidekiq::Web => "/sidekiq" end resource :sessions, only: %i[show create destroy] + resources :users, only: %i[show] namespace :webhooks do resources :github, only: %i[create]