mirror of
https://github.com/czhu12/canine.git
synced 2026-01-06 03:30:16 -06:00
added authentication schemes
This commit is contained in:
1
Gemfile
1
Gemfile
@@ -82,6 +82,7 @@ gem "pagy", "~> 9.4"
|
||||
gem "oj", "~> 3.16"
|
||||
gem "omniauth", "~> 2.1"
|
||||
gem "omniauth-rails_csrf_protection", "~> 1.0"
|
||||
gem "omniauth_openid_connect", "~> 0.8"
|
||||
|
||||
gem "annotate", "~> 3.2"
|
||||
|
||||
|
||||
50
Gemfile.lock
50
Gemfile.lock
@@ -97,10 +97,12 @@ GEM
|
||||
tzinfo (~> 2.0, >= 2.0.5)
|
||||
addressable (2.8.7)
|
||||
public_suffix (>= 2.0.2, < 7.0)
|
||||
aes_key_wrap (1.1.0)
|
||||
annotate (3.2.0)
|
||||
activerecord (>= 3.2, < 8.0)
|
||||
rake (>= 10.4, < 14.0)
|
||||
ast (2.4.3)
|
||||
attr_required (1.0.2)
|
||||
avo (3.25.3)
|
||||
actionview (>= 6.1)
|
||||
active_link_to
|
||||
@@ -122,6 +124,7 @@ GEM
|
||||
bcrypt (3.1.20)
|
||||
benchmark (0.5.0)
|
||||
bigdecimal (3.3.1)
|
||||
bindata (2.5.1)
|
||||
bindex (0.8.1)
|
||||
bootsnap (1.18.6)
|
||||
msgpack (~> 1.2)
|
||||
@@ -192,6 +195,8 @@ GEM
|
||||
dry-inflector (~> 1.0)
|
||||
dry-logic (~> 1.4)
|
||||
zeitwerk (~> 2.6)
|
||||
email_validator (2.2.4)
|
||||
activemodel
|
||||
erb (5.1.3)
|
||||
erubi (1.13.1)
|
||||
et-orbi (1.4.0)
|
||||
@@ -208,6 +213,8 @@ GEM
|
||||
faraday-net_http (>= 2.0, < 3.5)
|
||||
json
|
||||
logger
|
||||
faraday-follow_redirects (0.4.0)
|
||||
faraday (>= 1, < 3)
|
||||
faraday-net_http (3.4.1)
|
||||
net-http (>= 0.5.0)
|
||||
ffi (1.17.0-aarch64-linux-gnu)
|
||||
@@ -284,6 +291,13 @@ GEM
|
||||
jsbundling-rails (1.3.1)
|
||||
railties (>= 6.0.0)
|
||||
json (2.13.2)
|
||||
json-jwt (1.17.0)
|
||||
activesupport (>= 4.2)
|
||||
aes_key_wrap
|
||||
base64
|
||||
bindata
|
||||
faraday (~> 2.0)
|
||||
faraday-follow_redirects
|
||||
jsonpath (1.1.5)
|
||||
multi_json
|
||||
jwt (2.9.1)
|
||||
@@ -398,6 +412,22 @@ GEM
|
||||
omniauth-rails_csrf_protection (1.0.2)
|
||||
actionpack (>= 4.2)
|
||||
omniauth (~> 2.0)
|
||||
omniauth_openid_connect (0.8.0)
|
||||
omniauth (>= 1.9, < 3)
|
||||
openid_connect (~> 2.2)
|
||||
openid_connect (2.3.1)
|
||||
activemodel
|
||||
attr_required (>= 1.0.0)
|
||||
email_validator
|
||||
faraday (~> 2.0)
|
||||
faraday-follow_redirects
|
||||
json-jwt (>= 1.16)
|
||||
mail
|
||||
rack-oauth2 (~> 2.2)
|
||||
swd (~> 2.0)
|
||||
tzinfo
|
||||
validate_url
|
||||
webfinger (~> 2.0)
|
||||
orm_adapter (0.5.0)
|
||||
ostruct (0.6.2)
|
||||
pagy (9.4.0)
|
||||
@@ -432,6 +462,13 @@ GEM
|
||||
raabro (1.4.0)
|
||||
racc (1.8.1)
|
||||
rack (3.1.18)
|
||||
rack-oauth2 (2.3.0)
|
||||
activesupport
|
||||
attr_required
|
||||
faraday (~> 2.0)
|
||||
faraday-follow_redirects
|
||||
json-jwt (>= 1.11.0)
|
||||
rack (>= 2.1.0)
|
||||
rack-protection (4.0.0)
|
||||
base64 (>= 0.1.0)
|
||||
rack (>= 3.0.0, < 4)
|
||||
@@ -583,6 +620,11 @@ GEM
|
||||
stimulus-rails (1.3.4)
|
||||
railties (>= 6.0.0)
|
||||
stringio (3.1.7)
|
||||
swd (2.0.3)
|
||||
activesupport (>= 3)
|
||||
attr_required (>= 0.0.5)
|
||||
faraday (~> 2.0)
|
||||
faraday-follow_redirects
|
||||
sys-proctable (1.3.0)
|
||||
ffi (~> 1.1)
|
||||
tailwindcss-rails (2.7.6)
|
||||
@@ -613,6 +655,9 @@ GEM
|
||||
unicode-emoji (4.0.4)
|
||||
uri (1.0.3)
|
||||
useragent (0.16.11)
|
||||
validate_url (1.0.15)
|
||||
activemodel (>= 3.0.0)
|
||||
public_suffix
|
||||
version_gem (1.1.4)
|
||||
view_component (4.1.0)
|
||||
activesupport (>= 7.1.0, < 8.2)
|
||||
@@ -624,6 +669,10 @@ GEM
|
||||
activemodel (>= 6.0.0)
|
||||
bindex (>= 0.4.0)
|
||||
railties (>= 6.0.0)
|
||||
webfinger (2.1.3)
|
||||
activesupport
|
||||
faraday (~> 2.0)
|
||||
faraday-follow_redirects
|
||||
webmock (3.26.1)
|
||||
addressable (>= 2.8.0)
|
||||
crack (>= 0.3.2)
|
||||
@@ -683,6 +732,7 @@ DEPENDENCIES
|
||||
omniauth-github (~> 2.0)
|
||||
omniauth-gitlab (~> 4.1)
|
||||
omniauth-rails_csrf_protection (~> 1.0)
|
||||
omniauth_openid_connect (~> 0.8)
|
||||
pagy (~> 9.4)
|
||||
pg (~> 1.6)
|
||||
pretender (~> 0.6.0)
|
||||
|
||||
21
app/avo/resources/ldap_configuration.rb
Normal file
21
app/avo/resources/ldap_configuration.rb
Normal file
@@ -0,0 +1,21 @@
|
||||
class Avo::Resources::LdapConfiguration < Avo::BaseResource
|
||||
# self.includes = []
|
||||
# self.attachments = []
|
||||
# self.search = {
|
||||
# query: -> { query.ransack(id_eq: q, m: "or").result(distinct: false) }
|
||||
# }
|
||||
|
||||
def fields
|
||||
field :id, as: :id
|
||||
field :host, as: :text
|
||||
field :port, as: :number
|
||||
field :encryption, as: :text
|
||||
field :base_dn, as: :text
|
||||
field :bind_dn, as: :text
|
||||
field :bind_password, as: :text
|
||||
field :uid_attribute, as: :text
|
||||
field :email_attribute, as: :text
|
||||
field :name_attribute, as: :text
|
||||
field :filter, as: :text
|
||||
end
|
||||
end
|
||||
19
app/avo/resources/oidc_configuration.rb
Normal file
19
app/avo/resources/oidc_configuration.rb
Normal file
@@ -0,0 +1,19 @@
|
||||
class Avo::Resources::OIDCConfiguration < Avo::BaseResource
|
||||
# self.includes = []
|
||||
# self.attachments = []
|
||||
# self.search = {
|
||||
# query: -> { query.ransack(id_eq: q, m: "or").result(distinct: false) }
|
||||
# }
|
||||
|
||||
def fields
|
||||
field :id, as: :id
|
||||
field :issuer, as: :text
|
||||
field :client_id, as: :text
|
||||
field :client_secret, as: :text
|
||||
field :authorization_endpoint, as: :text
|
||||
field :token_endpoint, as: :text
|
||||
field :userinfo_endpoint, as: :text
|
||||
field :jwks_uri, as: :text
|
||||
field :scopes, as: :text
|
||||
end
|
||||
end
|
||||
15
app/avo/resources/sso_provider.rb
Normal file
15
app/avo/resources/sso_provider.rb
Normal file
@@ -0,0 +1,15 @@
|
||||
class Avo::Resources::SSOProvider < Avo::BaseResource
|
||||
# self.includes = []
|
||||
# self.attachments = []
|
||||
# self.search = {
|
||||
# query: -> { query.ransack(id_eq: q, m: "or").result(distinct: false) }
|
||||
# }
|
||||
|
||||
def fields
|
||||
field :id, as: :id
|
||||
field :account, as: :belongs_to
|
||||
field :configuration, as: :text
|
||||
field :name, as: :text
|
||||
field :enabled, as: :boolean
|
||||
end
|
||||
end
|
||||
71
app/controllers/accounts/sso_providers_controller.rb
Normal file
71
app/controllers/accounts/sso_providers_controller.rb
Normal file
@@ -0,0 +1,71 @@
|
||||
module Accounts
|
||||
class SSOProvidersController < ApplicationController
|
||||
def show
|
||||
@sso_provider = current_account.sso_provider
|
||||
@oidc_configuration = @sso_provider&.configuration
|
||||
end
|
||||
|
||||
def new
|
||||
@sso_provider = current_account.build_sso_provider
|
||||
@oidc_configuration = OIDCConfiguration.new
|
||||
end
|
||||
|
||||
def create
|
||||
@oidc_configuration = OIDCConfiguration.new(oidc_configuration_params)
|
||||
@sso_provider = current_account.build_sso_provider(sso_provider_params)
|
||||
@sso_provider.configuration = @oidc_configuration
|
||||
|
||||
if @oidc_configuration.save && @sso_provider.save
|
||||
redirect_to sso_provider_path, notice: "SSO provider created successfully"
|
||||
else
|
||||
render :new, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
@sso_provider = current_account.sso_provider
|
||||
redirect_to new_sso_provider_path, alert: "No SSO provider configured" unless @sso_provider
|
||||
@oidc_configuration = @sso_provider&.configuration
|
||||
end
|
||||
|
||||
def update
|
||||
@sso_provider = current_account.sso_provider
|
||||
@oidc_configuration = @sso_provider.configuration
|
||||
|
||||
if @oidc_configuration.update(oidc_configuration_params) && @sso_provider.update(sso_provider_params)
|
||||
redirect_to sso_provider_path, notice: "SSO provider updated successfully"
|
||||
else
|
||||
render :edit, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@sso_provider = current_account.sso_provider
|
||||
|
||||
if @sso_provider&.destroy
|
||||
redirect_to sso_provider_path, notice: "SSO provider deleted"
|
||||
else
|
||||
redirect_to sso_provider_path, alert: "Failed to delete SSO provider"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def sso_provider_params
|
||||
params.require(:sso_provider).permit(:name, :enabled)
|
||||
end
|
||||
|
||||
def oidc_configuration_params
|
||||
params.require(:oidc_configuration).permit(
|
||||
:issuer,
|
||||
:client_id,
|
||||
:client_secret,
|
||||
:authorization_endpoint,
|
||||
:token_endpoint,
|
||||
:userinfo_endpoint,
|
||||
:jwks_uri,
|
||||
:scopes
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
4
app/controllers/avo/ldap_configurations_controller.rb
Normal file
4
app/controllers/avo/ldap_configurations_controller.rb
Normal file
@@ -0,0 +1,4 @@
|
||||
# This controller has been generated to enable Rails' resource routes.
|
||||
# More information on https://docs.avohq.io/3.0/controllers.html
|
||||
class Avo::LdapConfigurationsController < Avo::ResourcesController
|
||||
end
|
||||
4
app/controllers/avo/oidc_configurations_controller.rb
Normal file
4
app/controllers/avo/oidc_configurations_controller.rb
Normal file
@@ -0,0 +1,4 @@
|
||||
# This controller has been generated to enable Rails' resource routes.
|
||||
# More information on https://docs.avohq.io/3.0/controllers.html
|
||||
class Avo::OIDCConfigurationsController < Avo::ResourcesController
|
||||
end
|
||||
4
app/controllers/avo/sso_providers_controller.rb
Normal file
4
app/controllers/avo/sso_providers_controller.rb
Normal file
@@ -0,0 +1,4 @@
|
||||
# This controller has been generated to enable Rails' resource routes.
|
||||
# More information on https://docs.avohq.io/3.0/controllers.html
|
||||
class Avo::SSOProvidersController < Avo::ResourcesController
|
||||
end
|
||||
@@ -1,7 +1,7 @@
|
||||
module Users
|
||||
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
||||
before_action :set_provider, except: [ :failure ]
|
||||
before_action :set_user, except: [ :failure ]
|
||||
before_action :set_provider, except: [ :failure, :oidc ]
|
||||
before_action :set_user, except: [ :failure, :oidc ]
|
||||
|
||||
attr_reader :provider, :user
|
||||
|
||||
@@ -13,8 +13,46 @@ module Users
|
||||
handle_auth "Github"
|
||||
end
|
||||
|
||||
def oidc
|
||||
sso_provider_id = session["sso_provider_id"]
|
||||
sso_provider = SSOProvider.find_by(id: sso_provider_id) if sso_provider_id.present?
|
||||
|
||||
if sso_provider
|
||||
@user = find_or_create_oidc_user
|
||||
handle_oidc_auth(sso_provider)
|
||||
else
|
||||
redirect_to root_path, alert: "SSO provider not found"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_or_create_oidc_user
|
||||
if user_signed_in?
|
||||
current_user
|
||||
else
|
||||
# Find or create user from OIDC data
|
||||
create_user
|
||||
end
|
||||
end
|
||||
|
||||
def handle_oidc_auth(sso_provider)
|
||||
# For OIDC, we don't store in Provider model, just authenticate
|
||||
if user_signed_in?
|
||||
flash[:notice] = "Your #{sso_provider.name} account was connected."
|
||||
redirect_to edit_user_registration_path
|
||||
else
|
||||
sign_in_and_redirect @user, event: :authentication
|
||||
# Set account from SSO provider
|
||||
session[:account_id] = sso_provider.account_id
|
||||
set_flash_message :notice, :success, kind: sso_provider.name
|
||||
end
|
||||
|
||||
# Clear SSO session data
|
||||
session.delete("sso_provider_id")
|
||||
session.delete("sso_account_id")
|
||||
end
|
||||
|
||||
def handle_auth(kind)
|
||||
if provider.present?
|
||||
provider.update(provider_attrs)
|
||||
|
||||
@@ -40,6 +40,7 @@ class Users::SessionsController < Devise::SessionsController
|
||||
def account_login
|
||||
self.resource = resource_class.new(sign_in_params)
|
||||
clean_up_passwords(resource)
|
||||
@sso_provider = @account.sso_provider if @account.sso_enabled?
|
||||
if @account.stack_manager&.portainer?
|
||||
render "devise/sessions/portainer"
|
||||
else
|
||||
|
||||
@@ -26,6 +26,7 @@ class Account < ApplicationRecord
|
||||
has_many :account_users, dependent: :destroy
|
||||
has_many :users, through: :account_users
|
||||
has_one :stack_manager, dependent: :destroy
|
||||
has_one :sso_provider, dependent: :destroy
|
||||
|
||||
has_many :clusters, dependent: :destroy
|
||||
has_many :build_clouds, through: :clusters
|
||||
@@ -47,4 +48,8 @@ class Account < ApplicationRecord
|
||||
def github_provider
|
||||
@_github_account ||= owner.providers.find_by(provider: "github")
|
||||
end
|
||||
|
||||
def sso_enabled?
|
||||
sso_provider&.enabled?
|
||||
end
|
||||
end
|
||||
|
||||
43
app/models/ldap_configuration.rb
Normal file
43
app/models/ldap_configuration.rb
Normal file
@@ -0,0 +1,43 @@
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: ldap_configurations
|
||||
#
|
||||
# id :bigint not null, primary key
|
||||
# base_dn :string not null
|
||||
# bind_dn :string
|
||||
# bind_password :string
|
||||
# email_attribute :string default("mail")
|
||||
# encryption :string default("plain")
|
||||
# filter :string
|
||||
# host :string not null
|
||||
# name_attribute :string default("cn")
|
||||
# port :integer default(389), not null
|
||||
# uid_attribute :string default("uid"), not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
class LdapConfiguration < ApplicationRecord
|
||||
has_one :sso_provider, as: :configuration, dependent: :destroy
|
||||
|
||||
validates :host, presence: true
|
||||
validates :port, presence: true, numericality: { only_integer: true, greater_than: 0 }
|
||||
validates :base_dn, presence: true
|
||||
validates :uid_attribute, presence: true
|
||||
validates :encryption, inclusion: { in: %w[plain simple_tls start_tls] }
|
||||
|
||||
# Encryption options: plain (no encryption), simple_tls (LDAPS), start_tls (STARTTLS)
|
||||
def encryption_method
|
||||
case encryption
|
||||
when "simple_tls"
|
||||
:simple_tls
|
||||
when "start_tls"
|
||||
:start_tls
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def requires_auth?
|
||||
bind_dn.present? && bind_password.present?
|
||||
end
|
||||
end
|
||||
19
app/models/oidc_configuration.rb
Normal file
19
app/models/oidc_configuration.rb
Normal file
@@ -0,0 +1,19 @@
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: oidc_configurations
|
||||
#
|
||||
# id :bigint not null, primary key
|
||||
# authorization_endpoint :string
|
||||
# client_secret :string not null
|
||||
# issuer :string not null
|
||||
# jwks_uri :string
|
||||
# scopes :string default("openid email profile")
|
||||
# token_endpoint :string
|
||||
# userinfo_endpoint :string
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# client_id :string not null
|
||||
#
|
||||
class OIDCConfiguration < ApplicationRecord
|
||||
has_one :sso_provider, as: :configuration, dependent: :destroy
|
||||
end
|
||||
36
app/models/sso_provider.rb
Normal file
36
app/models/sso_provider.rb
Normal file
@@ -0,0 +1,36 @@
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: sso_providers
|
||||
#
|
||||
# id :bigint not null, primary key
|
||||
# configuration_type :string not null
|
||||
# enabled :boolean default(TRUE), not null
|
||||
# name :string not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# account_id :bigint not null
|
||||
# configuration_id :bigint not null
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_sso_providers_on_account_id (account_id) UNIQUE
|
||||
# index_sso_providers_on_configuration (configuration_type,configuration_id)
|
||||
#
|
||||
# Foreign Keys
|
||||
#
|
||||
# fk_rails_... (account_id => accounts.id)
|
||||
#
|
||||
class SSOProvider < ApplicationRecord
|
||||
belongs_to :account
|
||||
belongs_to :configuration, polymorphic: true
|
||||
|
||||
validates :account_id, uniqueness: true
|
||||
|
||||
def oidc?
|
||||
configuration_type == "OIDCConfiguration"
|
||||
end
|
||||
|
||||
def ldap?
|
||||
configuration_type == "LdapConfiguration"
|
||||
end
|
||||
end
|
||||
98
app/views/accounts/sso_providers/_form.html.erb
Normal file
98
app/views/accounts/sso_providers/_form.html.erb
Normal file
@@ -0,0 +1,98 @@
|
||||
<%= form_with model: sso_provider, url: sso_provider.persisted? ? sso_provider_path : sso_provider_path do |form| %>
|
||||
<%= render "shared/error_messages", resource: form.object %>
|
||||
|
||||
<div class="form-control mt-4 w-full max-w-sm">
|
||||
<label class="label">
|
||||
<span class="label-text">Provider Name</span>
|
||||
</label>
|
||||
<%= form.text_field :name, class: "input input-bordered", required: true, placeholder: "e.g., Company SSO" %>
|
||||
<label class="label">
|
||||
<span class="label-text-alt">A friendly name for this SSO provider</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-control mt-1 w-full max-w-sm">
|
||||
<label class="label cursor-pointer justify-start gap-2">
|
||||
<%= form.check_box :enabled, class: "checkbox checkbox-primary" %>
|
||||
<span class="label-text">Enable this provider</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="divider">OIDC Configuration</div>
|
||||
|
||||
<%= fields_for :oidc_configuration, oidc_configuration do |oidc_form| %>
|
||||
<div class="form-control mt-1 w-full max-w-sm">
|
||||
<label class="label">
|
||||
<span class="label-text">Issuer URL <span class="text-error">*</span></span>
|
||||
</label>
|
||||
<%= oidc_form.text_field :issuer, class: "input input-bordered", required: true, placeholder: "https://your-idp.com" %>
|
||||
<label class="label">
|
||||
<span class="label-text-alt">The base URL of your OIDC provider</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-control mt-1 w-full max-w-sm">
|
||||
<label class="label">
|
||||
<span class="label-text">Client ID <span class="text-error">*</span></span>
|
||||
</label>
|
||||
<%= oidc_form.text_field :client_id, class: "input input-bordered", required: true %>
|
||||
</div>
|
||||
|
||||
<div class="form-control mt-1 w-full max-w-sm">
|
||||
<label class="label">
|
||||
<span class="label-text">Client Secret <span class="text-error">*</span></span>
|
||||
</label>
|
||||
<%= oidc_form.password_field :client_secret, class: "input input-bordered", required: true %>
|
||||
</div>
|
||||
|
||||
<div class="form-control mt-1 w-full max-w-sm">
|
||||
<label class="label">
|
||||
<span class="label-text">Scopes</span>
|
||||
</label>
|
||||
<%= oidc_form.text_field :scopes, class: "input input-bordered", placeholder: "openid email profile" %>
|
||||
<label class="label">
|
||||
<span class="label-text-alt">Space-separated list of scopes (defaults to "openid email profile")</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<details class="collapse collapse-arrow bg-base-200 mt-4 max-w-sm">
|
||||
<summary class="collapse-title font-medium">
|
||||
Advanced Settings (Optional)
|
||||
</summary>
|
||||
<div class="collapse-content space-y-4">
|
||||
<div class="form-control mt-1 w-full">
|
||||
<label class="label">
|
||||
<span class="label-text">Authorization Endpoint</span>
|
||||
</label>
|
||||
<%= oidc_form.text_field :authorization_endpoint, class: "input input-bordered", placeholder: "Auto-discovered if not set" %>
|
||||
</div>
|
||||
|
||||
<div class="form-control mt-1 w-full">
|
||||
<label class="label">
|
||||
<span class="label-text">Token Endpoint</span>
|
||||
</label>
|
||||
<%= oidc_form.text_field :token_endpoint, class: "input input-bordered", placeholder: "Auto-discovered if not set" %>
|
||||
</div>
|
||||
|
||||
<div class="form-control mt-1 w-full">
|
||||
<label class="label">
|
||||
<span class="label-text">UserInfo Endpoint</span>
|
||||
</label>
|
||||
<%= oidc_form.text_field :userinfo_endpoint, class: "input input-bordered", placeholder: "Auto-discovered if not set" %>
|
||||
</div>
|
||||
|
||||
<div class="form-control mt-1 w-full">
|
||||
<label class="label">
|
||||
<span class="label-text">JWKS URI</span>
|
||||
</label>
|
||||
<%= oidc_form.text_field :jwks_uri, class: "input input-bordered", placeholder: "Auto-discovered if not set" %>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
<% end %>
|
||||
|
||||
<div class="form-footer mt-6">
|
||||
<%= form.submit sso_provider.persisted? ? "Update Provider" : "Create Provider", class: "btn btn-primary" %>
|
||||
<%= link_to "Cancel", sso_provider_path, class: "btn btn-outline" %>
|
||||
</div>
|
||||
<% end %>
|
||||
13
app/views/accounts/sso_providers/edit.html.erb
Normal file
13
app/views/accounts/sso_providers/edit.html.erb
Normal file
@@ -0,0 +1,13 @@
|
||||
<%= settings_layout do %>
|
||||
<%= turbo_frame_tag "sso_provider" do %>
|
||||
<div class="font-lg font-bold mt-4">
|
||||
Edit <%= @sso_provider.name %>
|
||||
</div>
|
||||
|
||||
<div class="text-sm text-gray-500 mt-2 mb-4">
|
||||
Update your OIDC provider configuration.
|
||||
</div>
|
||||
|
||||
<%= render "accounts/sso_providers/form", sso_provider: @sso_provider, oidc_configuration: @oidc_configuration %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
13
app/views/accounts/sso_providers/new.html.erb
Normal file
13
app/views/accounts/sso_providers/new.html.erb
Normal file
@@ -0,0 +1,13 @@
|
||||
<%= settings_layout do %>
|
||||
<%= turbo_frame_tag "sso_provider" do %>
|
||||
<div class="font-lg font-bold mt-4">
|
||||
Add OIDC Provider
|
||||
</div>
|
||||
|
||||
<div class="text-sm text-gray-500 mt-2 mb-4">
|
||||
Configure an OpenID Connect (OIDC) provider for SSO authentication. This will allow users to sign in using your organization's identity provider.
|
||||
</div>
|
||||
|
||||
<%= render "accounts/sso_providers/form", sso_provider: @sso_provider, oidc_configuration: @oidc_configuration %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
55
app/views/accounts/sso_providers/show.html.erb
Normal file
55
app/views/accounts/sso_providers/show.html.erb
Normal file
@@ -0,0 +1,55 @@
|
||||
<%= settings_layout do %>
|
||||
<h2 class="text-2xl font-bold">Authentication</h2>
|
||||
<hr class="mt-3 mb-4 border-t border-base-300" />
|
||||
|
||||
<%= turbo_frame_tag "sso_provider" do %>
|
||||
<% if @sso_provider.present? %>
|
||||
<div class="card bg-base-200 mb-4">
|
||||
<div class="card-body">
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold"><%= @sso_provider.name %></h3>
|
||||
<div class="flex gap-2 mt-2">
|
||||
<span class="badge badge-outline">OIDC</span>
|
||||
<% if @sso_provider.enabled %>
|
||||
<span class="badge badge-success">Enabled</span>
|
||||
<% else %>
|
||||
<span class="badge badge-ghost">Disabled</span>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<%= link_to "Edit", edit_sso_provider_path, class: "btn btn-sm btn-outline" %>
|
||||
<%= button_to "Delete", sso_provider_path, method: :delete, class: "btn btn-sm btn-error", data: { turbo_confirm: "Are you sure you want to delete this SSO provider?" } %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% if @oidc_configuration %>
|
||||
<div class="divider"></div>
|
||||
<div class="grid grid-cols-2 gap-4 text-sm">
|
||||
<div>
|
||||
<span class="text-gray-500">Issuer:</span>
|
||||
<div class="font-mono"><%= @oidc_configuration.issuer %></div>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-gray-500">Client ID:</span>
|
||||
<div class="font-mono"><%= @oidc_configuration.client_id %></div>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-gray-500">Scopes:</span>
|
||||
<div class="font-mono"><%= @oidc_configuration.scopes || "openid email profile" %></div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="alert mb-4">
|
||||
<iconify-icon icon="lucide:info" class="mr-2"></iconify-icon>
|
||||
<span>No SSO provider configured for this account.</span>
|
||||
</div>
|
||||
|
||||
<%= link_to "+ Add OIDC Provider", new_sso_provider_path, class: "btn btn-primary btn-sm" %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
@@ -41,13 +41,27 @@
|
||||
<%= f.submit "Sign in", class: "btn btn-primary w-full" %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% unless Rails.application.config.local_mode %>
|
||||
|
||||
<% if defined?(@sso_provider) && @sso_provider.present? %>
|
||||
<div class="flex items-center my-4">
|
||||
<div class="flex-grow border-t border-gray-600"></div>
|
||||
<span class="mx-4 text-gray-500">OR</span>
|
||||
<div class="flex-grow border-t border-gray-600"></div>
|
||||
</div>
|
||||
|
||||
<%= button_to "Sign in with #{@sso_provider.name}",
|
||||
user_oidc_omniauth_authorize_path(sso_provider_id: @sso_provider.id),
|
||||
class: "btn btn-outline w-full",
|
||||
data: { turbo: false } %>
|
||||
<% end %>
|
||||
|
||||
<% unless Rails.application.config.local_mode %>
|
||||
<% unless defined?(@sso_provider) && @sso_provider.present? %>
|
||||
<div class="flex items-center my-4">
|
||||
<div class="flex-grow border-t border-gray-600"></div>
|
||||
<span class="mx-4 text-gray-500">OR</span>
|
||||
<div class="flex-grow border-t border-gray-600"></div>
|
||||
</div>
|
||||
<% end %>
|
||||
<%= render "devise/shared/links" %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
@@ -39,6 +39,12 @@
|
||||
Credentials
|
||||
<% end %>
|
||||
</li>
|
||||
<li class="<%= 'underline' if current_page?(sso_provider_path) || current_page?(new_sso_provider_path) || current_page?(edit_sso_provider_path) %>" style="text-underline-offset: 0.25rem;">
|
||||
<%= link_to sso_provider_path do %>
|
||||
<iconify-icon icon="lucide:shield-check" ></iconify-icon>
|
||||
Authentication
|
||||
<% end %>
|
||||
</li>
|
||||
<% if Flipper.enabled?(:stack_manager, current_account) %>
|
||||
<li class="<%= 'underline' if controller_name == 'stack_managers' %>" style="text-underline-offset: 0.25rem;">
|
||||
<%= link_to stack_manager_path do %>
|
||||
|
||||
@@ -274,14 +274,21 @@ Devise.setup do |config|
|
||||
# TODO (chris): add digital ocean?
|
||||
config.omniauth :github, ENV["OMNIAUTH_GITHUB_PUBLIC_KEY"], ENV["OMNIAUTH_GITHUB_PRIVATE_KEY"], scope: "user,repo,write:packages,read:org"
|
||||
config.omniauth :developer if Rails.env.test?
|
||||
|
||||
# Dynamic OIDC provider
|
||||
require Rails.root.join("lib/omniauth/strategies/dynamic_oidc")
|
||||
config.omniauth :oidc, strategy_class: OmniAuth::Strategies::DynamicOIDC
|
||||
|
||||
# ==> Warden configuration
|
||||
# If you want to use other strategies, that are not supported by Devise, or
|
||||
# change the failure app, you can configure them inside the config.warden block.
|
||||
#
|
||||
# config.warden do |manager|
|
||||
# manager.intercept_401 = false
|
||||
# manager.default_strategies(scope: :user).unshift :some_external_strategy
|
||||
# end
|
||||
# Load custom LDAP authentication strategy
|
||||
require Rails.root.join('lib', 'devise', 'strategies', 'ldap_authenticatable')
|
||||
|
||||
config.warden do |manager|
|
||||
manager.strategies.add(:ldap_authenticatable, Devise::Strategies::LdapAuthenticatable)
|
||||
end
|
||||
|
||||
# ==> Mountable engine configurations
|
||||
# When using Devise inside an engine, let's call it `MyEngine`, and this engine
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
# end
|
||||
|
||||
# These inflection rules are supported but not enabled by default:
|
||||
# ActiveSupport::Inflector.inflections(:en) do |inflect|
|
||||
# inflect.acronym "RESTful"
|
||||
# end
|
||||
ActiveSupport::Inflector.inflections(:en) do |inflect|
|
||||
inflect.acronym "OIDC"
|
||||
inflect.acronym "SSO"
|
||||
end
|
||||
|
||||
@@ -19,6 +19,7 @@ Rails.application.routes.draw do
|
||||
resources :accounts, only: [ :create ] do
|
||||
collection do
|
||||
resources :account_users, only: %i[create index destroy], module: :accounts
|
||||
resource :sso_provider, only: %i[show new create edit update destroy], module: :accounts
|
||||
end
|
||||
member do
|
||||
get :switch
|
||||
|
||||
12
db/migrate/20251121024059_create_sso_providers.rb
Normal file
12
db/migrate/20251121024059_create_sso_providers.rb
Normal file
@@ -0,0 +1,12 @@
|
||||
class CreateSSOProviders < ActiveRecord::Migration[7.2]
|
||||
def change
|
||||
create_table :sso_providers do |t|
|
||||
t.references :account, null: false, foreign_key: true, index: { unique: true }
|
||||
t.references :configuration, polymorphic: true, null: false
|
||||
t.string :name, null: false
|
||||
t.boolean :enabled, default: true, null: false
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
||||
16
db/migrate/20251121024136_create_oidc_configurations.rb
Normal file
16
db/migrate/20251121024136_create_oidc_configurations.rb
Normal file
@@ -0,0 +1,16 @@
|
||||
class CreateOIDCConfigurations < ActiveRecord::Migration[7.2]
|
||||
def change
|
||||
create_table :oidc_configurations do |t|
|
||||
t.string :issuer, null: false
|
||||
t.string :client_id, null: false
|
||||
t.string :client_secret, null: false
|
||||
t.string :authorization_endpoint
|
||||
t.string :token_endpoint
|
||||
t.string :userinfo_endpoint
|
||||
t.string :jwks_uri
|
||||
t.string :scopes, default: "openid email profile"
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
||||
18
db/migrate/20251121043926_create_ldap_configurations.rb
Normal file
18
db/migrate/20251121043926_create_ldap_configurations.rb
Normal file
@@ -0,0 +1,18 @@
|
||||
class CreateLdapConfigurations < ActiveRecord::Migration[7.2]
|
||||
def change
|
||||
create_table :ldap_configurations do |t|
|
||||
t.string :host, null: false
|
||||
t.integer :port, default: 389, null: false
|
||||
t.string :encryption, default: "plain"
|
||||
t.string :base_dn, null: false
|
||||
t.string :bind_dn
|
||||
t.string :bind_password
|
||||
t.string :uid_attribute, default: "uid", null: false
|
||||
t.string :email_attribute, default: "mail"
|
||||
t.string :name_attribute, default: "cn"
|
||||
t.string :filter
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
||||
28
db/schema.rb
generated
28
db/schema.rb
generated
@@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.2].define(version: 2025_11_16_091324) do
|
||||
ActiveRecord::Schema[7.2].define(version: 2025_11_21_024136) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
||||
@@ -375,6 +375,19 @@ ActiveRecord::Schema[7.2].define(version: 2025_11_16_091324) do
|
||||
t.index ["recipient_type", "recipient_id"], name: "index_noticed_notifications_on_recipient"
|
||||
end
|
||||
|
||||
create_table "oidc_configurations", force: :cascade do |t|
|
||||
t.string "issuer", null: false
|
||||
t.string "client_id", null: false
|
||||
t.string "client_secret", null: false
|
||||
t.string "authorization_endpoint"
|
||||
t.string "token_endpoint"
|
||||
t.string "userinfo_endpoint"
|
||||
t.string "jwks_uri"
|
||||
t.string "scopes", default: "openid email profile"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
end
|
||||
|
||||
create_table "project_add_ons", force: :cascade do |t|
|
||||
t.bigint "project_id", null: false
|
||||
t.bigint "add_on_id", null: false
|
||||
@@ -477,6 +490,18 @@ ActiveRecord::Schema[7.2].define(version: 2025_11_16_091324) do
|
||||
t.index ["project_id"], name: "index_services_on_project_id"
|
||||
end
|
||||
|
||||
create_table "sso_providers", force: :cascade do |t|
|
||||
t.bigint "account_id", null: false
|
||||
t.string "configuration_type", null: false
|
||||
t.bigint "configuration_id", null: false
|
||||
t.string "name", null: false
|
||||
t.boolean "enabled", default: true, null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["account_id"], name: "index_sso_providers_on_account_id", unique: true
|
||||
t.index ["configuration_type", "configuration_id"], name: "index_sso_providers_on_configuration"
|
||||
end
|
||||
|
||||
create_table "stack_managers", force: :cascade do |t|
|
||||
t.string "provider_url", null: false
|
||||
t.integer "stack_manager_type", default: 0, null: false
|
||||
@@ -544,6 +569,7 @@ ActiveRecord::Schema[7.2].define(version: 2025_11_16_091324) do
|
||||
add_foreign_key "projects", "clusters", column: "project_fork_cluster_id"
|
||||
add_foreign_key "providers", "users"
|
||||
add_foreign_key "services", "projects"
|
||||
add_foreign_key "sso_providers", "accounts"
|
||||
add_foreign_key "stack_managers", "accounts"
|
||||
add_foreign_key "volumes", "projects"
|
||||
end
|
||||
|
||||
79
lib/devise/strategies/ldap_authenticatable.rb
Normal file
79
lib/devise/strategies/ldap_authenticatable.rb
Normal file
@@ -0,0 +1,79 @@
|
||||
require 'net/ldap'
|
||||
require 'devise/strategies/authenticatable'
|
||||
|
||||
module Devise
|
||||
module Strategies
|
||||
class LdapAuthenticatable < Authenticatable
|
||||
def authenticate!
|
||||
if params[:user]
|
||||
ldap_config = find_ldap_configuration
|
||||
|
||||
return fail(:invalid_login) unless ldap_config
|
||||
|
||||
ldap = Net::LDAP.new(
|
||||
host: ldap_config.host,
|
||||
port: ldap_config.port,
|
||||
encryption: ldap_config.encryption_method
|
||||
)
|
||||
|
||||
# Build the user DN for authentication
|
||||
user_dn = build_user_dn(ldap_config, email)
|
||||
ldap.auth user_dn, password
|
||||
|
||||
if ldap.bind
|
||||
# LDAP authentication successful, find or create user
|
||||
user = User.find_or_create_by(email: email) do |u|
|
||||
u.password = SecureRandom.hex(32) # Set random password since LDAP handles auth
|
||||
u.account = find_or_create_account_for_ldap(ldap_config)
|
||||
end
|
||||
success!(user)
|
||||
else
|
||||
Rails.logger.info "LDAP bind failed for #{email}: #{ldap.get_operation_result.message}"
|
||||
return fail(:invalid_login)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def email
|
||||
params[:user][:email]
|
||||
end
|
||||
|
||||
def password
|
||||
params[:user][:password]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_ldap_configuration
|
||||
# Find the LDAP configuration for the account
|
||||
# This could be based on email domain or a selection during login
|
||||
account_id = session[:ldap_account_id] || params[:account_id]
|
||||
|
||||
if account_id
|
||||
sso_provider = SSOProvider.find_by(account_id: account_id, enabled: true)
|
||||
return sso_provider.configuration if sso_provider&.ldap?
|
||||
end
|
||||
|
||||
# Fallback: try to find by email domain or return first enabled LDAP config
|
||||
LdapConfiguration.joins(:sso_provider)
|
||||
.where(sso_providers: { enabled: true })
|
||||
.first
|
||||
end
|
||||
|
||||
def build_user_dn(ldap_config, email)
|
||||
# Extract username from email if needed
|
||||
username = email.split('@').first
|
||||
|
||||
# Build the DN using the uid attribute and base DN
|
||||
"#{ldap_config.uid_attribute}=#{username},#{ldap_config.base_dn}"
|
||||
end
|
||||
|
||||
def find_or_create_account_for_ldap(ldap_config)
|
||||
sso_provider = ldap_config.sso_provider
|
||||
sso_provider&.account
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Warden::Strategies.add(:ldap_authenticatable, Devise::Strategies::LdapAuthenticatable)
|
||||
61
lib/omniauth/strategies/dynamic_oidc.rb
Normal file
61
lib/omniauth/strategies/dynamic_oidc.rb
Normal file
@@ -0,0 +1,61 @@
|
||||
require "omniauth_openid_connect"
|
||||
|
||||
module OmniAuth
|
||||
module Strategies
|
||||
class DynamicOIDC < OmniAuth::Strategies::OpenIDConnect
|
||||
option :name, "oidc"
|
||||
option :issuer, "placeholder" # Will be overridden
|
||||
|
||||
# Run before request_phase to load config from database
|
||||
def request_phase
|
||||
load_configuration_from_database
|
||||
super
|
||||
end
|
||||
|
||||
# Run before callback_phase to load config from database
|
||||
def callback_phase
|
||||
load_configuration_from_database
|
||||
super
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_configuration_from_database
|
||||
sso_provider_id = request.params["sso_provider_id"] || session["sso_provider_id"]
|
||||
|
||||
return unless sso_provider_id.present?
|
||||
|
||||
begin
|
||||
sso_provider = SSOProvider.find_by(id: sso_provider_id, enabled: true)
|
||||
|
||||
if sso_provider&.oidc? && sso_provider.configuration
|
||||
oidc_config = sso_provider.configuration
|
||||
|
||||
# Override the options with database values
|
||||
options[:issuer] = oidc_config.issuer
|
||||
options[:client_options] = {
|
||||
identifier: oidc_config.client_id,
|
||||
secret: oidc_config.client_secret,
|
||||
redirect_uri: callback_url,
|
||||
authorization_endpoint: oidc_config.authorization_endpoint.presence,
|
||||
token_endpoint: oidc_config.token_endpoint.presence,
|
||||
userinfo_endpoint: oidc_config.userinfo_endpoint.presence,
|
||||
jwks_uri: oidc_config.jwks_uri.presence
|
||||
}.compact
|
||||
|
||||
options[:scope] = oidc_config.scopes&.split(" ") || [ :openid, :email, :profile ]
|
||||
options[:response_type] = :code
|
||||
options[:uid_field] = "sub"
|
||||
|
||||
# Store in session for callback
|
||||
session["sso_provider_id"] = sso_provider.id
|
||||
session["sso_account_id"] = sso_provider.account_id
|
||||
end
|
||||
rescue ActiveRecord::StatementInvalid, PG::UndefinedTable
|
||||
# Database not ready yet or table doesn't exist
|
||||
Rails.logger.debug "DynamicOIDC: database not ready"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
14
spec/factories/ldap_configurations.rb
Normal file
14
spec/factories/ldap_configurations.rb
Normal file
@@ -0,0 +1,14 @@
|
||||
FactoryBot.define do
|
||||
factory :ldap_configuration do
|
||||
host { "MyString" }
|
||||
port { 1 }
|
||||
encryption { "MyString" }
|
||||
base_dn { "MyString" }
|
||||
bind_dn { "MyString" }
|
||||
bind_password { "MyString" }
|
||||
uid_attribute { "MyString" }
|
||||
email_attribute { "MyString" }
|
||||
name_attribute { "MyString" }
|
||||
filter { "MyString" }
|
||||
end
|
||||
end
|
||||
28
spec/factories/oidc_configurations.rb
Normal file
28
spec/factories/oidc_configurations.rb
Normal file
@@ -0,0 +1,28 @@
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: oidc_configurations
|
||||
#
|
||||
# id :bigint not null, primary key
|
||||
# authorization_endpoint :string
|
||||
# client_secret :string not null
|
||||
# issuer :string not null
|
||||
# jwks_uri :string
|
||||
# scopes :string default("openid email profile")
|
||||
# token_endpoint :string
|
||||
# userinfo_endpoint :string
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# client_id :string not null
|
||||
#
|
||||
FactoryBot.define do
|
||||
factory :oidc_configuration do
|
||||
issuer { "MyString" }
|
||||
client_id { "MyString" }
|
||||
client_secret { "MyString" }
|
||||
authorization_endpoint { "MyString" }
|
||||
token_endpoint { "MyString" }
|
||||
userinfo_endpoint { "MyString" }
|
||||
jwks_uri { "MyString" }
|
||||
scopes { "MyString" }
|
||||
end
|
||||
end
|
||||
30
spec/factories/sso_providers.rb
Normal file
30
spec/factories/sso_providers.rb
Normal file
@@ -0,0 +1,30 @@
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: sso_providers
|
||||
#
|
||||
# id :bigint not null, primary key
|
||||
# configuration_type :string not null
|
||||
# enabled :boolean default(TRUE), not null
|
||||
# name :string not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# account_id :bigint not null
|
||||
# configuration_id :bigint not null
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_sso_providers_on_account_id (account_id) UNIQUE
|
||||
# index_sso_providers_on_configuration (configuration_type,configuration_id)
|
||||
#
|
||||
# Foreign Keys
|
||||
#
|
||||
# fk_rails_... (account_id => accounts.id)
|
||||
#
|
||||
FactoryBot.define do
|
||||
factory :sso_provider do
|
||||
account { nil }
|
||||
configuration { nil }
|
||||
name { "MyString" }
|
||||
enabled { false }
|
||||
end
|
||||
end
|
||||
5
spec/models/ldap_configuration_spec.rb
Normal file
5
spec/models/ldap_configuration_spec.rb
Normal file
@@ -0,0 +1,5 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe LdapConfiguration, type: :model do
|
||||
pending "add some examples to (or delete) #{__FILE__}"
|
||||
end
|
||||
21
spec/models/oidc_configuration_spec.rb
Normal file
21
spec/models/oidc_configuration_spec.rb
Normal file
@@ -0,0 +1,21 @@
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: oidc_configurations
|
||||
#
|
||||
# id :bigint not null, primary key
|
||||
# authorization_endpoint :string
|
||||
# client_secret :string not null
|
||||
# issuer :string not null
|
||||
# jwks_uri :string
|
||||
# scopes :string default("openid email profile")
|
||||
# token_endpoint :string
|
||||
# userinfo_endpoint :string
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# client_id :string not null
|
||||
#
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe OIDCConfiguration, type: :model do
|
||||
pending "add some examples to (or delete) #{__FILE__}"
|
||||
end
|
||||
@@ -18,6 +18,14 @@
|
||||
# updated_at :datetime not null
|
||||
# project_id :bigint not null
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_services_on_project_id (project_id)
|
||||
#
|
||||
# Foreign Keys
|
||||
#
|
||||
# fk_rails_... (project_id => projects.id)
|
||||
#
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Service, type: :model do
|
||||
|
||||
27
spec/models/sso_provider_spec.rb
Normal file
27
spec/models/sso_provider_spec.rb
Normal file
@@ -0,0 +1,27 @@
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: sso_providers
|
||||
#
|
||||
# id :bigint not null, primary key
|
||||
# configuration_type :string not null
|
||||
# enabled :boolean default(TRUE), not null
|
||||
# name :string not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# account_id :bigint not null
|
||||
# configuration_id :bigint not null
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_sso_providers_on_account_id (account_id) UNIQUE
|
||||
# index_sso_providers_on_configuration (configuration_type,configuration_id)
|
||||
#
|
||||
# Foreign Keys
|
||||
#
|
||||
# fk_rails_... (account_id => accounts.id)
|
||||
#
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe SSOProvider, type: :model do
|
||||
pending "add some examples to (or delete) #{__FILE__}"
|
||||
end
|
||||
Reference in New Issue
Block a user