leveret_auth 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a85472d6ff5fae2cf5ea2c09fb16fdc2a01fca37bfd5b5deb863a8bc99c924ba
4
+ data.tar.gz: ffb0fcf672c4ed65af3063b2d83371e1b8004d0bf0c582c246343ecfd73b27f5
5
+ SHA512:
6
+ metadata.gz: 2c93c85be104deec37f841ba5f7dd1b8681c1fe1cfcf52d1809765de49aa891182249acd04d3fef4a8e6234326933558c841d882d245de89f39a81de6983d07b
7
+ data.tar.gz: d1e439f8fd6c1068c6c7e33459e6a608f5983e587f5c7b853af834d65b7c7a43ce6b24c42e114a0c9644b798a1f6f0e217cd6402cf4c2d685722fc049dfce6ba
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Devise
4
+ module Models
5
+ module AuthIdentitable
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ has_many :identities, class_name: 'LeveretAuth::Identities',
10
+ foreign_key: :user_id, dependent: :destroy
11
+ end
12
+
13
+ module ClassMethods
14
+ def setup_user_from_third_party(provider:, uid:, email:)
15
+ raise LeveretAuth::Errors::ThirdPartyNotProvideEmail if email.nil? || email.empty?
16
+
17
+ identity = LeveretAuth::Identities.find_or_initialize_by(uid: uid, provider: provider)
18
+ return identity.user unless identity.new_record?
19
+
20
+ identity.user = setup_with_temporary_passsword(email)
21
+ identity.save!
22
+ identity.user
23
+ end
24
+
25
+ private
26
+
27
+ def setup_with_temporary_passsword(email)
28
+ user = find_or_initialize_by(email: email)
29
+ user unless user.new_record?
30
+
31
+ user.password = LeveretAuth.configuration.user_default_password
32
+ user.skip_confirmation!
33
+ user.save!
34
+ user
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+ require 'rails/generators/active_record'
5
+
6
+ module LeveretAuth
7
+ class MigrationGenerator < ::Rails::Generators::Base
8
+ include ::Rails::Generators::Migration
9
+ source_root File.expand_path('templates', __dir__)
10
+ desc 'Installs LeveretAuth migration file.'
11
+
12
+ def install
13
+ migration_template(
14
+ 'migration.rb.erb',
15
+ 'db/migrate/create_leveret_auth_tables.rb',
16
+ migration_version: migration_version
17
+ )
18
+ end
19
+
20
+ def self.next_migration_number(dirname)
21
+ ActiveRecord::Generators::Base.next_migration_number(dirname)
22
+ end
23
+
24
+ private
25
+
26
+ def migration_version
27
+ "[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateLeveretAuthTables < ActiveRecord::Migration<%= migration_version %>
4
+ def change
5
+ create_table :identities do |t|
6
+ t.string :uid, null: false
7
+ t.string :provider, null: false
8
+ t.references :user, null: false
9
+ t.timestamps
10
+ end
11
+
12
+ add_index :identities, %i[uid provider]
13
+
14
+ # Uncomment below to ensure a valid reference to the user's table
15
+ # add_foreign_key :identities, <model>, column: :user_id
16
+ end
17
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LeveretAuth
4
+ class MissingConfiguration < StandardError
5
+ def initialize
6
+ super('Configuration for leveret auth missing. Do you have on-premise auth initializer?')
7
+ end
8
+ end
9
+
10
+ class << self
11
+ def setup
12
+ run_orm_hooks
13
+ add_extension_to_devise
14
+ end
15
+
16
+ def configuration
17
+ @configuration || (raise MissingConfiguration)
18
+ end
19
+
20
+ def configure(&block)
21
+ @configuration = Config::Builder.new(&block).build
22
+ end
23
+
24
+ private
25
+
26
+ def run_orm_hooks
27
+ LeveretAuth::Orm::ActiveRecord.run_hooks
28
+ end
29
+
30
+ def add_extension_to_devise
31
+ Devise.add_module :auth_identitable, model: 'devise/models/auth_identitable'
32
+ end
33
+ end
34
+
35
+ # Default DiviseAuth configuration
36
+ class Config
37
+ def providers
38
+ @providers ||= []
39
+ end
40
+
41
+ def user_model_name
42
+ @user_model_name.to_s.classify
43
+ end
44
+
45
+ def user_model
46
+ @user_model ||= user_model_name.constantize
47
+ end
48
+
49
+ def user_default_password
50
+ @user_default_password
51
+ end
52
+
53
+ def identities_model
54
+ @identities_model ||= 'LeveretAuth::Identities'.constantize
55
+ end
56
+
57
+ # Default DiviseAuth configuration builder
58
+ class Builder
59
+ def initialize(config = Config.new, &block)
60
+ @config = config
61
+ instance_eval(&block)
62
+ end
63
+
64
+ def build
65
+ validate
66
+ @config
67
+ end
68
+
69
+ def validate
70
+ if @config.user_default_password.nil?
71
+ raise ArgumentError,
72
+ 'Must configure user default password by call `user_default_password password`'
73
+ end
74
+ end
75
+
76
+ def devise_for(model_name)
77
+ @config.instance_variable_set(:@user_model_name, model_name)
78
+ end
79
+
80
+ def user_default_password(password)
81
+ @config.instance_variable_set(:@user_default_password, password)
82
+ end
83
+
84
+ def add_provider(name, **opts)
85
+ opts = Utils.load_json_file(opts[:file_path]) if opts[:file_path]
86
+ begin
87
+ klass = Strategies.const_get("#{name.to_s.camelize}Strategy")
88
+ rescue NameError
89
+ raise LoadError, "Could not find matching strategy for #{name}."
90
+ end
91
+ @config.providers << name.downcase
92
+ klass.configure(opts.compact)
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,7 @@
1
+ module LeveretAuth
2
+ class Engine < Rails::Engine
3
+ config.to_prepare do
4
+ LeveretAuth.setup
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LeveretAuth
4
+ module Errors
5
+ class StrategyNotFound < StandardError; end
6
+ class ThirdPartyNotProvideEmail < StandardError; end
7
+ class InvalidCredential < StandardError; end
8
+ end
9
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LeveretAuth
4
+ module Ldap
5
+ class Configuration
6
+ class ConfigurationError < StandardError; end
7
+
8
+ # For build Net::LDAP::Connection
9
+ REQUIRED_CONNECTION_CONFIG_KEYS = %i[host port base].freeze
10
+ OPTIONAL_CONNECTION_CONFIG_KEYS = [
11
+ encryption: %i[method tls_opitons],
12
+ auth: %i[method username password]
13
+ ].freeze
14
+
15
+ # For custom search condition
16
+ REQUIRED_SEARCH_CONFIG_KEYS = [%i[uid filter]].freeze
17
+
18
+ attr_reader :uid, :filter
19
+
20
+ def initialize(configuration = {})
21
+ validate_required_keys(configuration)
22
+
23
+ permitted_keys = [permitted_connection_keys + permitted_search_keys].flatten
24
+ Utils.deep_slice(configuration, permitted_keys).each do |k, v|
25
+ instance_variable_set("@#{k}", v)
26
+ end
27
+
28
+ validate_encryption
29
+ validate_auth
30
+ end
31
+
32
+ def connection_config
33
+ permitted_connection_keys.each_with_object({}) do |key, hash|
34
+ if key.is_a?(Hash)
35
+ key.each_key { |sub_key| hash[sub_key] = instance_variable_get("@#{sub_key}") }
36
+ else
37
+ hash[key] = instance_variable_get("@#{key}")
38
+ end
39
+ hash
40
+ end.compact
41
+ end
42
+
43
+ private
44
+
45
+ def validate_required_keys(configuration)
46
+ required_keys = REQUIRED_CONNECTION_CONFIG_KEYS + REQUIRED_SEARCH_CONFIG_KEYS
47
+ missing_keys = Utils.validate(configuration, required_keys)
48
+
49
+ raise ConfigurationError, Utils.build_error_message(missing_keys) unless missing_keys.empty?
50
+ end
51
+
52
+ def validate_encryption
53
+ return if @encryption.nil?
54
+ return unless @encryption.is_a?(Hash)
55
+
56
+ @encryption[:method] = @encryption[:method].to_sym
57
+ return if %i[simple_tls start_tls].include?(@encryption[:method])
58
+
59
+ tls_options = @encryption[:tls_options]
60
+ return if tls_options.nil?
61
+
62
+ @encryption[:tls_options] = ::OpenSSL::SSL::SSLContext::DEFAULT_PARAMS.merge(tls_options)
63
+
64
+ raise ConfigurationError, "unsupported encryption method #{@encryption[:method]}"
65
+ end
66
+
67
+ def validate_auth
68
+ return if @auth.nil?
69
+
70
+ @auth[:method] = @auth[:method].to_sym
71
+ if %i[simple anonymous].exclude?(@auth[:method])
72
+ raise ConfigurationError, "unsupported auth method #{@auth[:method]}"
73
+ end
74
+
75
+ if @auth[:method] == :simple
76
+ return if @auth[:username] && @auth[:password]
77
+
78
+ raise ConfigurationError, 'simple auth must have username and password'
79
+ end
80
+ end
81
+
82
+ def permitted_connection_keys
83
+ REQUIRED_CONNECTION_CONFIG_KEYS + OPTIONAL_CONNECTION_CONFIG_KEYS
84
+ end
85
+
86
+ def permitted_search_keys
87
+ REQUIRED_SEARCH_CONFIG_KEYS
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/ldap'
4
+
5
+ module LeveretAuth
6
+ module Ldap
7
+ class Connection
8
+ def initialize(configuration)
9
+ @configuration = if configuration.is_a?(Configuration)
10
+ configuration
11
+ else
12
+ Configuration.new(configuration)
13
+ end
14
+ @connection = Net::LDAP.new(@configuration.connection_config)
15
+ end
16
+
17
+ def search(value)
18
+ @connection.search(filter: build_filter(value))
19
+ end
20
+
21
+ def bind_as(entry_dn, password)
22
+ @connection.bind(method: :simple, username: entry_dn, password: password)
23
+ end
24
+
25
+ private
26
+
27
+ def build_filter(value)
28
+ if @configuration.filter && !@configuration.filter.empty?
29
+ Net::LDAP::Filter.construct(@configuration.filter % { username: value })
30
+ else
31
+ Net::LDAP::Filter.eq(@configuration.uid, value)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,7 @@
1
+ # require 'leveret_auth/orm/active_record/mixins/identities'
2
+
3
+ module LeveretAuth
4
+ class Identities < ::ActiveRecord::Base
5
+ include LeveretAuth::Orm::ActiveRecord::Mixins::Identities
6
+ end
7
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LeveretAuth::Orm::ActiveRecord::Mixins
4
+ module Identities
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ self.table_name = 'identities'
9
+ self.strict_loading_by_default = false if respond_to?(:strict_loading_by_default)
10
+
11
+ belongs_to :user, class_name: "::#{LeveretAuth.configuration.user_model_name}",
12
+ inverse_of: :identities
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+ # require 'leveret_auth/orm/active_record/identities'
3
+
4
+ module LeveretAuth
5
+ autoload :Identities, 'leveret_auth/orm/active_record/identities'
6
+
7
+ module Orm
8
+ module ActiveRecord
9
+ module Mixins
10
+ autoload :Identities, 'leveret_auth/orm/active_record/mixins/identities'
11
+ end
12
+
13
+ def self.run_hooks
14
+ models.each(&:establish_connection)
15
+ end
16
+
17
+ def self.models
18
+ [
19
+ LeveretAuth.configuration.identities_model
20
+ ]
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,35 @@
1
+ module LeveretAuth
2
+ module Strategies
3
+ class BaseStrategy
4
+ class << self
5
+ def config
6
+ @config ||= {}
7
+ end
8
+
9
+ def configure(configuration)
10
+ @config = configuration
11
+ end
12
+ end
13
+
14
+ def initialize(params)
15
+ permitted_attrs.each do |key|
16
+ instance_variable_set("@#{key}", params[key])
17
+ end
18
+ end
19
+
20
+ def authenticate!
21
+ raise 'Must implement the method: `authenticate!`'
22
+ end
23
+
24
+ private
25
+
26
+ def user_model
27
+ LeveretAuth.configuration.user_model
28
+ end
29
+
30
+ def permitted_attrs
31
+ raise 'Must implement method: `permitted_attrs`'
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,21 @@
1
+ require 'leveret_auth/strategies/base_strategy'
2
+
3
+ module LeveretAuth
4
+ module Strategies
5
+ class DeviseStrategy < BaseStrategy
6
+ def authenticate!
7
+ user = user_model.find_for_authentication(email: @email)
8
+ raise Errors::InvalidCredential if user.nil?
9
+ raise Errors::InvalidCredential unless user.valid_for_authentication? { user.valid_password?(@password) }
10
+
11
+ user
12
+ end
13
+
14
+ private
15
+
16
+ def permitted_attrs
17
+ %i[email password]
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,41 @@
1
+ require 'leveret_auth/strategies/base_strategy'
2
+ require 'leveret_auth/ldap/configuration'
3
+ require 'leveret_auth/ldap/connection'
4
+
5
+ module LeveretAuth
6
+ module Strategies
7
+ class LdapStrategy < BaseStrategy
8
+ class << self
9
+ def client
10
+ @client ||= Ldap::Connection.new(config)
11
+ end
12
+
13
+ def configure(configuration)
14
+ @config = Ldap::Configuration.new(configuration)
15
+ end
16
+ end
17
+
18
+ def authenticate!
19
+ entrys = client.search(@email)
20
+ raise Errors::InvalidCredential if entrys.nil?
21
+
22
+ verified_entry = entrys.find { |entry| client.bind_as(entry.dn, @password) }
23
+ raise Errors::InvalidCredential if verified_entry.nil?
24
+
25
+ user_model.setup_user_from_third_party(uid: verified_entry.dn,
26
+ provider: 'ldap',
27
+ email: @email)
28
+ end
29
+
30
+ private
31
+
32
+ def permitted_attrs
33
+ %i[email password]
34
+ end
35
+
36
+ def client
37
+ self.class.client
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,42 @@
1
+ module LeveretAuth
2
+ module Utils
3
+ class << self
4
+ def load_json_file(file_path)
5
+ file = File.open(Rails.root.join(file_path), 'r').read
6
+ JSON.parse(file).deep_symbolize_keys
7
+ end
8
+
9
+ def validate(hash, required_keys)
10
+ required_keys.select do |key|
11
+ if key.is_a?(Array)
12
+ hash.slice(*key).empty?
13
+ else
14
+ hash[key].nil?
15
+ end
16
+ end
17
+ end
18
+
19
+ def build_error_message(missing_keys)
20
+ error_message = missing_keys.map do |key|
21
+ key.is_a?(Array) ? key.join(' or ') : key
22
+ end
23
+ "Missing keys: #{error_message.join(', ')}"
24
+ end
25
+
26
+ def deep_slice(hash, keys)
27
+ return unless hash.is_a?(Hash)
28
+
29
+ keys.each_with_object({}) do |k, new_hash|
30
+ if k.is_a?(Hash)
31
+ k.each do |sub_key, sub_value|
32
+ new_hash[sub_key] = deep_slice(hash[sub_key], sub_value) if hash.key?(sub_key)
33
+ end
34
+ else
35
+ new_hash[k] = hash[k] if hash.key?(k)
36
+ end
37
+ new_hash
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'leveret_auth/utils'
4
+ require 'leveret_auth/config'
5
+ require 'leveret_auth/engine'
6
+
7
+ # Main LeveretAuth namespace.
8
+ #
9
+ module LeveretAuth
10
+ autoload :Errors, 'leveret_auth/errors'
11
+
12
+ module Strategies
13
+ autoload :LdapStrategy, 'leveret_auth/strategies/ldap_strategy'
14
+ autoload :DeviseStrategy, 'leveret_auth/strategies/devise_strategy'
15
+ end
16
+
17
+ module Orm
18
+ autoload :ActiveRecord, 'leveret_auth/orm/active_record'
19
+ end
20
+
21
+ class << self
22
+ def auth_with_doorkeeper(params)
23
+ strategy_class = find_strategy(params[:grant_type], provider: params[:provider])
24
+ strategy = strategy_class.new(params)
25
+ strategy.authenticate!
26
+ end
27
+
28
+ private
29
+
30
+ def find_strategy(grant_type, provider: nil)
31
+ dispathcer_name = "#{grant_type.to_s.downcase}_strategy"
32
+ raise Errors::StrategyNotFound unless respond_to?(dispathcer_name, true)
33
+
34
+ method(dispathcer_name).call(provider)
35
+ end
36
+
37
+ def password_strategy(provider)
38
+ return Strategies::DeviseStrategy if provider.nil?
39
+ raise Errors::StrategyNotFound unless allowed_provider?(provider)
40
+
41
+ const_get("Strategies::#{provider.to_s.camelize}Strategy")
42
+ end
43
+
44
+ def allowed_provider?(provider)
45
+ return false if provider.nil?
46
+
47
+ configuration.providers.include?(provider.downcase.to_sym)
48
+ end
49
+ end
50
+ end
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: leveret_auth
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - ChengChih Chang
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-01-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: net-ldap
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: devise
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 4.8.1
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 4.8.1
41
+ description: auth with different strategy
42
+ email: cc.chang@kdanmobile.com
43
+ executables: []
44
+ extensions: []
45
+ extra_rdoc_files: []
46
+ files:
47
+ - lib/devise/models/auth_identitable.rb
48
+ - lib/generators/leveret_auth/migration_generator.rb
49
+ - lib/generators/leveret_auth/templates/migration.rb.erb
50
+ - lib/leveret_auth.rb
51
+ - lib/leveret_auth/config.rb
52
+ - lib/leveret_auth/engine.rb
53
+ - lib/leveret_auth/errors.rb
54
+ - lib/leveret_auth/ldap/configuration.rb
55
+ - lib/leveret_auth/ldap/connection.rb
56
+ - lib/leveret_auth/orm/active_record.rb
57
+ - lib/leveret_auth/orm/active_record/identities.rb
58
+ - lib/leveret_auth/orm/active_record/mixins/identities.rb
59
+ - lib/leveret_auth/strategies/base_strategy.rb
60
+ - lib/leveret_auth/strategies/devise_strategy.rb
61
+ - lib/leveret_auth/strategies/ldap_strategy.rb
62
+ - lib/leveret_auth/utils.rb
63
+ homepage: https://github.com/kdan-mobile-software-ltd/leveret_auth
64
+ licenses:
65
+ - MIT
66
+ metadata: {}
67
+ post_install_message:
68
+ rdoc_options: []
69
+ require_paths:
70
+ - lib
71
+ required_ruby_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: 2.7.0
76
+ required_rubygems_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ requirements: []
82
+ rubygems_version: 3.2.22
83
+ signing_key:
84
+ specification_version: 4
85
+ summary: The devise extension for auth strategy
86
+ test_files: []