diff options
Diffstat (limited to 'lib/api/v3/github.rb')
-rw-r--r-- | lib/api/v3/github.rb | 232 |
1 files changed, 232 insertions, 0 deletions
diff --git a/lib/api/v3/github.rb b/lib/api/v3/github.rb new file mode 100644 index 00000000000..593f90460ac --- /dev/null +++ b/lib/api/v3/github.rb @@ -0,0 +1,232 @@ +# frozen_string_literal: true + +# These endpoints partially mimic Github API behavior in order to successfully +# integrate with Jira Development Panel. +# Endpoints returning an empty list were temporarily added to avoid 404's +# during Jira's DVCS integration. +# +module API + module V3 + class Github < Grape::API::Instance + NO_SLASH_URL_PART_REGEX = %r{[^/]+}.freeze + ENDPOINT_REQUIREMENTS = { + namespace: NO_SLASH_URL_PART_REGEX, + project: NO_SLASH_URL_PART_REGEX, + username: NO_SLASH_URL_PART_REGEX + }.freeze + + # Used to differentiate Jira Cloud requests from Jira Server requests + # Jira Cloud user agent format: Jira DVCS Connector Vertigo/version + # Jira Server user agent format: Jira DVCS Connector/version + JIRA_DVCS_CLOUD_USER_AGENT = 'Jira DVCS Connector Vertigo'.freeze + + include PaginationParams + + before do + authorize_jira_user_agent!(request) + authenticate! + end + + helpers do + params :project_full_path do + requires :namespace, type: String + requires :project, type: String + end + + def authorize_jira_user_agent!(request) + not_found! unless Gitlab::Jira::Middleware.jira_dvcs_connector?(request.env) + end + + def update_project_feature_usage_for(project) + # Prevent errors on GitLab Geo not allowing + # UPDATE statements to happen in GET requests. + return if Gitlab::Database.read_only? + + project.log_jira_dvcs_integration_usage(cloud: jira_cloud?) + end + + def jira_cloud? + request.env['HTTP_USER_AGENT'].include?(JIRA_DVCS_CLOUD_USER_AGENT) + end + + def find_project_with_access(params) + project = find_project!( + ::Gitlab::Jira::Dvcs.restore_full_path(params.slice(:namespace, :project).symbolize_keys) + ) + not_found! unless can?(current_user, :download_code, project) + project + end + + # rubocop: disable CodeReuse/ActiveRecord + def find_merge_requests + merge_requests = authorized_merge_requests.reorder(updated_at: :desc) + paginate(merge_requests) + end + # rubocop: enable CodeReuse/ActiveRecord + + # rubocop: disable CodeReuse/ActiveRecord + def find_merge_request_with_access(id, access_level = :read_merge_request) + merge_request = authorized_merge_requests.find_by(id: id) + not_found! unless can?(current_user, access_level, merge_request) + merge_request + end + # rubocop: enable CodeReuse/ActiveRecord + + def authorized_merge_requests + MergeRequestsFinder.new(current_user, authorized_only: !current_user.admin?).execute + end + + def authorized_merge_requests_for_project(project) + MergeRequestsFinder.new(current_user, authorized_only: !current_user.admin?, project_id: project.id).execute + end + + # rubocop: disable CodeReuse/ActiveRecord + def find_notes(noteable) + # They're not presented on Jira Dev Panel ATM. A comments count with a + # redirect link is presented. + notes = paginate(noteable.notes.user.reorder(nil)) + notes.select { |n| n.readable_by?(current_user) } + end + # rubocop: enable CodeReuse/ActiveRecord + end + + resource :orgs do + get ':namespace/repos' do + present [] + end + end + + resource :user do + get :repos do + present [] + end + end + + resource :users do + params do + use :pagination + end + + get ':namespace/repos' do + namespace = Namespace.find_by_full_path(params[:namespace]) + not_found!('Namespace') unless namespace + + projects = current_user.can_read_all_resources? ? Project.all : current_user.authorized_projects + projects = projects.in_namespace(namespace.self_and_descendants) + + projects_cte = Project.wrap_with_cte(projects) + .eager_load_namespace_and_owner + .with_route + + present paginate(projects_cte), + with: ::API::Github::Entities::Repository, + root_namespace: namespace.root_ancestor + end + + get ':username' do + forbidden! unless can?(current_user, :read_users_list) + user = UsersFinder.new(current_user, { username: params[:username] }).execute.first + not_found! unless user + present user, with: ::API::Github::Entities::User + end + end + + # Jira dev panel integration weirdly requests for "/-/jira/pulls" instead + # "/api/v3/repos/<namespace>/<project>/pulls". This forces us into + # returning _all_ Merge Requests from authorized projects (user is a member), + # instead just the authorized MRs from a project. + # Jira handles the filtering, presenting just MRs mentioning the Jira + # issue ID on the MR title / description. + resource :repos do + # Keeping for backwards compatibility with old Jira integration instructions + # so that users that do not change it will not suddenly have a broken integration + get '/-/jira/pulls' do + present find_merge_requests, with: ::API::Github::Entities::PullRequest + end + + get '/-/jira/events' do + present [] + end + + params do + use :project_full_path + end + get ':namespace/:project/pulls' do + user_project = find_project_with_access(params) + + merge_requests = authorized_merge_requests_for_project(user_project) + + present paginate(merge_requests), with: ::API::Github::Entities::PullRequest + end + + params do + use :project_full_path + end + get ':namespace/:project/pulls/:id' do + merge_request = find_merge_request_with_access(params[:id]) + + present merge_request, with: ::API::Github::Entities::PullRequest + end + + # In Github, each Merge Request is automatically also an issue. + # Therefore we return its comments here. + # It'll present _just_ the comments counting with a link to GitLab on + # Jira dev panel, not the actual note content. + get ':namespace/:project/issues/:id/comments' do + merge_request = find_merge_request_with_access(params[:id]) + + present find_notes(merge_request), with: ::API::Github::Entities::NoteableComment + end + + # This refer to "review" comments but Jira dev panel doesn't seem to + # present it accordingly. + get ':namespace/:project/pulls/:id/comments' do + present [] + end + + # Commits are not presented within "Pull Requests" modal on Jira dev + # panel. + get ':namespace/:project/pulls/:id/commits' do + present [] + end + + # Self-hosted Jira (tested on 7.11.1) requests this endpoint right + # after fetching branches. + get ':namespace/:project/events' do + user_project = find_project_with_access(params) + + merge_requests = authorized_merge_requests_for_project(user_project) + + present paginate(merge_requests), with: ::API::Github::Entities::PullRequestEvent + end + + params do + use :project_full_path + use :pagination + end + get ':namespace/:project/branches' do + user_project = find_project_with_access(params) + + update_project_feature_usage_for(user_project) + + branches = ::Kaminari.paginate_array(user_project.repository.branches.sort_by(&:name)) + + present paginate(branches), with: ::API::Github::Entities::Branch, project: user_project + end + + params do + use :project_full_path + end + get ':namespace/:project/commits/:sha' do + user_project = find_project_with_access(params) + + commit = user_project.commit(params[:sha]) + + not_found! 'Commit' unless commit + + present commit, with: ::API::Github::Entities::RepoCommit + end + end + end + end +end |