Commit db07a143 authored by Drew Blessing's avatar Drew Blessing Committed by Adam Hegyi

Add Atlassian Identity to store identity/credentials

The new Atlassian Identity model and associated database table
will allow GitLab users to connect their account to Atlassian
Cloud. GitLab will then store the OAuth token and refresh token
so GitLab can interact with Atlassian JIRA via the API. This will
enable further integrations beyond authentication in the future.
parent ee0e3709
# frozen_string_literal: true
module Atlassian
class Identity < ApplicationRecord
self.table_name = 'atlassian_identities'
belongs_to :user
validates :extern_uid, presence: true, uniqueness: true
validates :user, presence: true, uniqueness: true
attr_encrypted :token,
mode: :per_attribute_iv,
key: Settings.attr_encrypted_db_key_base_truncated,
algorithm: 'aes-256-gcm',
encode: false,
encode_iv: false
attr_encrypted :refresh_token,
mode: :per_attribute_iv,
key: Settings.attr_encrypted_db_key_base_truncated,
algorithm: 'aes-256-gcm',
encode: false,
encode_iv: false
end
end
......@@ -181,6 +181,7 @@ class User < ApplicationRecord
has_one :user_detail
has_one :user_highest_role
has_one :user_canonical_email
has_one :atlassian_identity, class_name: 'Atlassian::Identity'
has_many :reviews, foreign_key: :author_id, inverse_of: :author
......
---
title: Add Atlassian Identity to store identity/credentials
merge_request: 40176
author:
type: added
# frozen_string_literal: true
class CreateAtlassianIdentities < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
unless table_exists?(:atlassian_identities)
with_lock_retries do
create_table :atlassian_identities, id: false do |t|
t.references :user, index: false, foreign_key: { on_delete: :cascade }, null: false, primary_key: true
t.timestamps_with_timezone
t.datetime_with_timezone :expires_at
t.text :extern_uid, null: false, index: { unique: true }
t.binary :encrypted_token
t.binary :encrypted_token_iv
t.binary :encrypted_refresh_token
t.binary :encrypted_refresh_token_iv
end
end
end
add_text_limit :atlassian_identities, :extern_uid, 255
add_check_constraint :atlassian_identities, 'octet_length(encrypted_token) <= 2048', 'atlassian_identities_token_length_constraint'
add_check_constraint :atlassian_identities, 'octet_length(encrypted_token_iv) <= 12', 'atlassian_identities_token_iv_length_constraint'
add_check_constraint :atlassian_identities, 'octet_length(encrypted_refresh_token) <= 512', 'atlassian_identities_refresh_token_length_constraint'
add_check_constraint :atlassian_identities, 'octet_length(encrypted_refresh_token_iv) <= 12', 'atlassian_identities_refresh_token_iv_length_constraint'
end
def down
with_lock_retries do
drop_table :atlassian_identities
end
end
end
d92cdef33a892fdd1761d9491bc8e4c782e9db348d4a6848a1470e99e644fbfd
\ No newline at end of file
......@@ -9479,6 +9479,32 @@ CREATE TABLE public.ar_internal_metadata (
updated_at timestamp(6) without time zone NOT NULL
);
CREATE TABLE public.atlassian_identities (
user_id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
expires_at timestamp with time zone,
extern_uid text NOT NULL,
encrypted_token bytea,
encrypted_token_iv bytea,
encrypted_refresh_token bytea,
encrypted_refresh_token_iv bytea,
CONSTRAINT atlassian_identities_refresh_token_iv_length_constraint CHECK ((octet_length(encrypted_refresh_token_iv) <= 12)),
CONSTRAINT atlassian_identities_refresh_token_length_constraint CHECK ((octet_length(encrypted_refresh_token) <= 512)),
CONSTRAINT atlassian_identities_token_iv_length_constraint CHECK ((octet_length(encrypted_token_iv) <= 12)),
CONSTRAINT atlassian_identities_token_length_constraint CHECK ((octet_length(encrypted_token) <= 2048)),
CONSTRAINT check_32f5779763 CHECK ((char_length(extern_uid) <= 255))
);
CREATE SEQUENCE public.atlassian_identities_user_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE public.atlassian_identities_user_id_seq OWNED BY public.atlassian_identities.user_id;
CREATE TABLE public.audit_events (
id integer NOT NULL,
author_id integer NOT NULL,
......@@ -16880,6 +16906,8 @@ ALTER TABLE ONLY public.approver_groups ALTER COLUMN id SET DEFAULT nextval('pub
ALTER TABLE ONLY public.approvers ALTER COLUMN id SET DEFAULT nextval('public.approvers_id_seq'::regclass);
ALTER TABLE ONLY public.atlassian_identities ALTER COLUMN user_id SET DEFAULT nextval('public.atlassian_identities_user_id_seq'::regclass);
ALTER TABLE ONLY public.audit_events ALTER COLUMN id SET DEFAULT nextval('public.audit_events_id_seq'::regclass);
ALTER TABLE ONLY public.award_emoji ALTER COLUMN id SET DEFAULT nextval('public.award_emoji_id_seq'::regclass);
......@@ -17800,6 +17828,9 @@ ALTER TABLE ONLY public.approvers
ALTER TABLE ONLY public.ar_internal_metadata
ADD CONSTRAINT ar_internal_metadata_pkey PRIMARY KEY (key);
ALTER TABLE ONLY public.atlassian_identities
ADD CONSTRAINT atlassian_identities_pkey PRIMARY KEY (user_id);
ALTER TABLE ONLY public.audit_events_part_5fc467ac26
ADD CONSTRAINT audit_events_part_5fc467ac26_pkey PRIMARY KEY (id, created_at);
......@@ -19237,6 +19268,8 @@ CREATE INDEX index_approvers_on_target_id_and_target_type ON public.approvers US
CREATE INDEX index_approvers_on_user_id ON public.approvers USING btree (user_id);
CREATE UNIQUE INDEX index_atlassian_identities_on_extern_uid ON public.atlassian_identities USING btree (extern_uid);
CREATE INDEX index_audit_events_on_entity_id_entity_type_id_desc_author_id ON public.audit_events USING btree (entity_id, entity_type, id DESC, author_id);
CREATE INDEX index_award_emoji_on_awardable_type_and_awardable_id ON public.award_emoji USING btree (awardable_type, awardable_id);
......@@ -23097,6 +23130,9 @@ ALTER TABLE ONLY public.resource_weight_events
ALTER TABLE ONLY public.design_management_designs
ADD CONSTRAINT fk_rails_bfe283ec3c FOREIGN KEY (issue_id) REFERENCES public.issues(id) ON DELETE CASCADE;
ALTER TABLE ONLY public.atlassian_identities
ADD CONSTRAINT fk_rails_c02928bc18 FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;
ALTER TABLE ONLY public.serverless_domain_cluster
ADD CONSTRAINT fk_rails_c09009dee1 FOREIGN KEY (pages_domain_id) REFERENCES public.pages_domains(id) ON DELETE CASCADE;
......
# frozen_string_literal: true
FactoryBot.define do
factory :atlassian_identity, class: 'Atlassian::Identity' do
extern_uid { generate(:username) }
user { create(:user) }
expires_at { 2.weeks.from_now }
token { SecureRandom.alphanumeric(1254) }
refresh_token { SecureRandom.alphanumeric(45) }
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Atlassian::Identity do
describe 'associations' do
it { is_expected.to belong_to(:user) }
end
describe 'validations' do
subject { create(:atlassian_identity) }
it { is_expected.to validate_presence_of(:extern_uid) }
it { is_expected.to validate_uniqueness_of(:extern_uid) }
it { is_expected.to validate_presence_of(:user) }
it { is_expected.to validate_uniqueness_of(:user) }
end
describe 'encrypted tokens' do
let(:token) { SecureRandom.alphanumeric(1254) }
let(:refresh_token) { SecureRandom.alphanumeric(45) }
let(:identity) { create(:atlassian_identity, token: token, refresh_token: refresh_token) }
it 'saves the encrypted token, refresh token and corresponding ivs' do
expect(identity.encrypted_token).not_to be_nil
expect(identity.encrypted_token_iv).not_to be_nil
expect(identity.encrypted_refresh_token).not_to be_nil
expect(identity.encrypted_refresh_token_iv).not_to be_nil
expect(identity.token).to eq(token)
expect(identity.refresh_token).to eq(refresh_token)
end
end
end
......@@ -68,6 +68,7 @@ RSpec.describe User do
it { is_expected.to have_one(:namespace) }
it { is_expected.to have_one(:status) }
it { is_expected.to have_one(:user_detail) }
it { is_expected.to have_one(:atlassian_identity) }
it { is_expected.to have_one(:user_highest_role) }
it { is_expected.to have_many(:snippets).dependent(:destroy) }
it { is_expected.to have_many(:members) }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment