I'm trying to modify an existing rails application that uses devise to check against an LDAP connection. I need to check against multiple different LDAP connections. Basically my user base is split between 2 or 3 different active directories and I'd like to be able to supply an array of connection information objects and have it run through the connections until it gets a response or fails. Is this possible with devise?
It is! Kind of. I hacked together a solution recently, not sure if it will be much help to you now.
First, you need to use devise_ldap_authenticatable. Once you have this installed, you can make some updates to the initialize method in the connection.rb file in this gem to accept either one configuration or many.
def initialize(params = {})
ldap_configs = YAML.load(ERB.new(File.read(::Devise.ldap_config || "#{Rails.root}/config/ldap.yml")).result)[Rails.env]
ldap_configs = ldap_configs.is_a?(Hash) ? [ldap_configs] : ldap_configs
The next part is up to you. Due to my constraints (usernames existing in both directories), I forced a user to enter a valid domain before looping through the connections. You might not have this constraint. In either case, just loop through the configs. Once you bind successfully, break the loop. The config values will be stored in the #ldap variable that is initialized here -
ldap_configs.each do |ldap_config|
#Maybe not needed if you don't have usernames in each directory. #domain is a user-entered value
if #domain == ldap_config["domain"]
#This should all stay the same, until you check the bindings
if #ldap.bind
#If it binds, break the loop. the values in #ldap will be stored
break
else
#keep looping
end
end
end
Next, make sure the ldap.yml file that devise_ldap_authenticatable generated is configured with all of your connections, including the domain, if needed -
## Environment
development:
-
host: "localhost1.com"
port: "389"
attribute: uid
base: dc=my-domain,dc=com
admin_user: cn=admin,dc=my-domain,dc=com
admin_password: admin_password
ssl: false
domain: "FIRST"
-
host: "localhost2.com"
port: "389"
attribute: uid
base: dc=my-domain,dc=com
admin_user: cn=admin,dc=my-domain,dc=com
admin_password: admin_password
ssl: false
domain: "SECOND"
I built on Steve's answer with the following that seems to work well. The benefit with this is that it wraps the original code and adds functionality to it. You can keep the ldap.yml file the same and add a hosts key with an array of hosts to the YAML to exercise this.
Note that I rescue the connection error in the loop. It will still throw when it attempts, again, to make the connection that the library would already try to make.
module Devise
module LDAP
module ConnectionExtensions
def initialize(params = {})
super
ldap_config = YAML.load(File.read("#{Rails.root}/config/ldap.yml"))[Rails.env]
ldap_config["hosts"]&.each do |host|
begin
#ldap.host = host
break if #ldap.bind
rescue Net::LDAP::Error => e
DeviseLdapAuthenticatable::Logger.send(e)
next
end
end
end
end
class Connection
prepend ConnectionExtensions
end
end
end
And here is the sample YAML file:
development:
host: localhost1.com
hosts:
- localhost1.com
- localhost2.com
port: 389
attribute: uid
base: dc=my-domain,dc=com
admin_user: cn=admin,dc=my-domain,dc=com
admin_password: admin_password
ssl: false
Related
I'm testing out the backup gem
http://backup.github.io/backup/v4/utilities/
I understand that I've to create a db_backup.rb with the configuration for example
Model.new(:my_backup, 'My Backup') do
database MySQL do |db|
# To dump all databases, set `db.name = :all` (or leave blank)
db.name = "my_database_name"
db.username = "my_username"
db.password = "my_password"
db.host = "localhost"
db.port = 3306
However I'm not able to find out how to get those details from the Rails database.yml. I've tried something like this:
env = defined?(RAILS_ENV) ? RAILS_ENV : 'development'
#settings = YAML.load(File.read(File.join( "config", "database.yml")))
But I guess there should be a better way.
I would do something like this:
env = defined?(RAILS_ENV) ? RAILS_ENV : 'development'
config = YAML.load_file(File.join('config', 'database.yml'))[env]
Model.new(:my_backup, 'My Backup') do
database MySQL do |db|
config.each_pair do |key, value|
db.public_send("#{key}=", value)
end
# ...
Use ActiveRecord's own configuration handling:
require 'active_record'
require 'yaml'
Model.new(:my_backup, 'My Backup') do
database MySQL do |db|
config = {
# these are the default values
host: 'localhost'
port: 3306
}.merge(load_configuration(ENV['RAILS_ENV'] || 'development'))
config.each_pair do |key, value|
db.public_send("#{key}=", value)
end
end
# this loads the configuration from file and memoizes it
def load_configuration(env)
#file_config ||= YAML.load(File.read(File.join( "config", "database.yml")))
#configurations ||= ActiveRecord::ConnectionHandling::MergeAndResolveDefaultUrlConfig.new(file_config).resolve
#configurations[env]
end
end
The key advantage here is that it will merge the values from ENV['DATABASE_URL']. Which is very important since you should avoid adding database credentials to config/database.yml.
A good habit is to specify only the connection adapter and base essentials in database.yml. Use ENV['DATABASE_URL'] for usernames, passwords and everything else.
Env vars are easy to change between deploys without changing any
code; unlike config files, there is little chance of them being
checked into the code repo accidentally; and unlike custom config
files, or other config mechanisms such as Java System Properties, they
are a language- and OS-agnostic standard.
- https://12factor.net/config
See:
Configuring Rails Applications
I have several sidekiq workers in my rails 4 app and can't figure out why the processes are failing. I am passing the User_id to the worker as a string but it can't seem to find the user. But when I search the same id in my console it finds the user. What am i doing wrong? Here is my code.
Controller.rb
def update
#user = current_user
if #user.bill.update_attributes(bill_params)
RandomWorker.perform_async(#user.id.to_s)
redirect_to users_dashboard_path, :notice => "Payment Informaton Updated"
else
...
end
end
Randomworker.rb
def perform(user_id)
user_id = user_id["$oid"] unless user_id.is_a?(String)
user = User.find(user_id)
user.process.update_report
user.process.save
end
My error comes back as
RandomWorker "575a..." Mongoid::Errors::DocumentNotFound: message: Document(s) not found for class User with id(s) 575a.... summary: When calling User.find with an id or array of ids, each parameter...
--EDIT--
My db config file is as shown:
development:
adapter: sqlite3
database: db/development.sqlite3
pool: 5
timeout: 5000
production:
adapter: sqlite3
database: db/production.sqlite3
pool: 25
timeout: 5000
And my mongoid.yml
development:
# Configure available database clients. (required)
clients:
# Defines the default client. (required)
default:
# Defines the name of the default database that Mongoid can connect to.
# (required).
database: development_db
# Provides the hosts the default client can connect to. Must be an array
# of host:port pairs. (required)
hosts:
- localhost:27017
options:
I found a solution. I had to put a monkeypatch in the sidekiq initalizer to treat the user.id as json. Apparently, mongoid struggles with this with sidekiq and although I struggled to find some documentation on it I stumbed across the answer in another unrelated question. Have to_json return a mongoid as a string
I added this in the initalizer and it seems to have fixed the issue
class BSON::ObjectId
def as_json
self.to_s
end
end
I'd advise that you include the Sidekiq::Web to view the enqueued jobs on a web interface so you can see the parameters and possibly the failures triggered.
However, this is an error I also faced a while ago too, which quite frustrated me for a while because of the number of emails I received from my error notifier(Bugsnag).
I've not found the best solution yet, but I imagine that the database was being locked for reading or some records weren't committed before attempting to use them.
Sidekiq's documentation says in your model use, after_commit :perform_sidekiq_job, on: :create.
However, I've not really tried that because I found another approach, the flip side to my new approach is that my jobs are executed much later, at times about 10minutes later.
RandomWorker.perform_in(5.seconds, user_id)
This is less likely to fail with the Sidekiq::retry option.
Read more here
I have a Sidekiq worker that is intended to perform social actions (e.g.: like pages on Facebook). Part of this requires knowing the URL for the object being liked.
Fortunately, Rails 3 makes it easy to access app-specific routes by including Rails.application.routes.url_helpers in whatever class or module needs access to the path/url helper method.
The problem I'm running into is that my default url/port are not accessible from within my Sidekiq worker despite various attempts to define them in my development.rb or production.rb.
class Facebook::LikeRecipeWorker
include Sidekiq::Worker
include Rails.application.routes.url_helpers
sidekiq_options queue: :facebook
def perform(recipe_id, user_id)
recipe = Recipe.find(recipe_id)
user = User.find(user_id)
if user.facebook_token
api = Koala::Facebook::API.new(user.facebook_token)
api.put_connections 'me', 'my_namespace:like', object: recipe_url(recipe)
end
end
end
When the recipe_url method is access, an ArgumentError is raised with the message:
ArgumentError: Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true
I know that I can specify default_url_options for ActionController or ActionMailer in the environment-specific config files, e.g.:
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
config.action_controller.default_url_options = { host: 'localhost', port: 3000 }
However, these (rightfully) appear to have no influence on my Sidekiq worker classes. How should I go about defining default_url_options for these classes?
What we did was this:
In your config file(s) (e.g. config/application.rb, config/production.db, etc.), have a line that sets the default:
routes.default_url_options = { host: 'www.example.org' }
I found a potential solution to this, though it feels like a little bit of a hack. I'm definitely open to better answers.
First, in my environment files (e.g.: config/environments/development.rb), I specify the default_url_options for my controllers:
config.action_controller.default_url_options = { host: 'localhost', port: 3000 }
Then in my worker class, I define a default_url_options instance method there:
class Facebook::LikeRecipeWorker
# ...
private
def default_url_options
ActionController::Base.default_url_options
end
end
So... First, I should make my goal clear. My goal is to have an environment defined constant filled with attributes from a yaml file that my app can reference.
The simplest example I can give is something like this:
#
initalizers/config.rb
CONFIG = YAML.load_file("#{Rails.root}/config/config.yml")[RAILS_ENV]
#
config.yml:
production:
host:
foo.com
development:
host:
localhost
port:
3000
#
config/environments/development.rb
Foo::Application.configure do
#...
config.action_mailer.default_url_options = { :host => CONFIG[:host], :port => CONFIG[:port] }
config.after_initialize do
Rails.application.routes.default_url_options = { :host => CONFIG[:host], :port => CONFIG[:port] }
end
end
My problems are:
The constant "CONFIG" does not exist prior to the after_initialize block (obviously because CONFIG is set as part of the initialize process), so I need to move this into the after_initialize block, but I cannot reference "config.action_mailer" inside the after_initialize block because "config" does not exist in that scope... Which is really confusing to me. Shouldn't "config" be accessible inside the block since it exists outside of it?
And as a side question, I am really really really confused how this config.x business works. The block is not yielding any variables so, how is "config" even valid in the context of Foo::Application.configure ?
I would think for it to work at all that it should be:
Foo::Application.configure do |config|
But that's not the case, so I really am curious how this works..
If you define your configuration loader inside of config/application.rb instead of an initializer you should be able to pre-empt the call to configure and have everything set up in time.
Sometimes it's best to make a sub-class that handles configuration files. A very basic example is this:
class Foo::Configuration
def initialize
# Read in contents of config file
end
def method_missing(name)
#config[name.to_s]
end
end
Then in your application you define a method like this:
class Foo::Application
def self.config
#config ||= Foo::Configuration.new
end
end
This will auto-load as required. It also avoids having a messy constant that shows up in the context of every single object.
Im trying to set up a Devise Authenticable LDAP login system. Right now, I can get it to work using all users. However, i would like to only use users within a certain group. To illustrate, as of now, using all users, the code looks like this:
production:
host: my.host.domain.com
port: 389
attribute: AccountName
base: cn=users,dc=my,dc=con,dc=to,dc=host
admin_user: adminuser
admin_password: password
ssl: false
So the following is the code i wrote to make it only work within the group "demo2" located within users. However, now it doesnt work with ANY user... Any suggestions?
production:
host: my.host.domain.com
port: 389
attribute: AccountName
base: cn=demo2,cn=users,dc=my,dc=con,dc=to,dc=host
admin_user: adminuser
admin_password: password
ssl: false
I believe you want to use required_groups in your ldap.yml. If you look at the template file you can see the example:
group_base: ou=groups,dc=test,dc=com
## Requires config.ldap_check_group_membership in devise.rb be true
# Can have multiple values, must match all to be authorized required_groups:
# If only a group name is given, membership will be checked against "uniqueMember"
- cn=admins,ou=groups,dc=test,dc=com