leveret_auth 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []