Ruby-Rails net-ldap search - ruby-on-rails

I want to be able to determine if a user is a memberOf a specific group as well as a couple other attributes available on the active directory. I know that the authentication and binding is working as intended, however, I keep getting two warnings:
warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
warning: The called method tcp is defined here
Since these are warnings, I wouldn't expect them to affect the output that I'm looking for.
I want to list out the attributes and the values for the search query, but when I run the code, I get the warnings, followed by "Bind succeeded!", and then the program finishes executing. I've tried changing the query, changing the attributes that I'm searching for, and ensuring that they were named correctly. I have posted the code below:
ldap = Net::LDAP.new :host => HOST, # your LDAP host name or IP goes here,
:port => PORT, # your LDAP host port goes here,
:encryption => :simple_tls,
:base => BASE# the base of your AD tree goes here,
:auth => {
:method => :simple,
:username => "#{login}##{myDomain}", # a user w/sufficient privileges to read from AD goes here,
:password => PASSWORD # the user's password goes here
}
search_param = #username
result_attrs = ["sAMAccountName", "displayName", "mail", "memberOf"] # Whatever you want to bring back in your result set goes here
# Build filter
search_filter = Net::LDAP::Filter.eq("sAMAccountName", search_param)
# Execute search
if ldap.bind
puts "Bind succeeded!"
ldap.search(:filter => search_filter, :attributes => result_attrs) do |entry|
puts "DN: #{entry.dn}"
entry.each do |attr, values|
puts ".......#{attr}:"
values.each do |value|
puts "#{value}\n"
end
end
end
else
puts "Failure to bind"
end
A couple problems I feel could be causing this issue is that I don't have the required authentication to search for users in the directory, or the users don't exist (I am searching for my own username, so it should exist, therefore I'm leaning to first theory). Thank you!

Related

Filtering Rails validation errors with multiple languages (I18n)

I want to intercept the validation errors thrown by Rails on a registration form. In English this works like this:
if #user.save
# Do whatever...
else
#errors = #user.errors.full_messages
if !#errors.index("Email has already been taken").nil?
# Present the user with a 'forgot password' UI
else
# Display the error on the screen
end
end
Since the test for a particular error is based on a hard-coded string comparison, it's a bit tenuous but it works when I only have a handful of exception cases.
However this becomes impossibly unwieldy when using more than one language, as the validation errors have already been localized and I'd have to use something monstrous like this:
if !#errors.index("Email has already been taken").nil? ||
!#errors.index("Email n'est pas disponible").nil? ||
!#errors.index("Email wurde bereits angenommen").nil? || ...
I'd like to think that Rails could give me a status code (like HTTP does) so I don't need to compare strings, but I haven't seen that documented anywhere.
What's the best workaround?
You should be able to get a few more details by interrogating #user.errors.details. It gets a brief mention in the guide to validations.
Essentially you should see something like this when the email is taken:
> user.errors.details
=> { :email => [{:error => :taken, :value => 'exists#example.org'}] }
That would allow your check for a "taken" email to be:
if #errors.details[:email] &&
#errors.details[:email].any? { |error| error[:error] == :taken }

Can not set :DELETE flag on GMail Inbox using rails mail gem

I am getting this error when trying to set the deleted flag;
Net::IMAP::NoResponseError (STORE attempt on READ-ONLY folder (Failure))
The error is thrown when running this;
connector.uid_store(item_uid, "+FLAGS", [:Deleted])
This code runs fine just before it;
connector.create("TestFolder") unless connector.list('', "TestFolder")
connector.uid_copy(item_uid, "TestFolder")
I have not been able to find a reason for this, especially since I can create 'folders' and copy items to it without a problem.
I am using ruby 1.9.2, rails 3.2.10, mail 2.4.4
Any help would really save my mind.
Cheers
~~~~~~~ edit
Mail defaults are setup as per below;
#==> Collect items
case feed.url_type
when "IMAP"
puts "Trying IMAP retriever for " + feed.url_source
Mail.defaults do
retriever_method :imap,
:address => feed.url_source,
:port => 993,
:user_name => feed.user,
:password => feed.password,
:enable_ssl => true,
:read_only => false
end
self.add_email_stubs(Mail.find(), feed)
The connector is picked up from here;
def add_email_stubs(items, feed)
Mail.all do |item, connector, item_uid|
and used here (in same def);
#==> Move message
connector.create("Archive") unless connector.list('', "Archive")
connector.uid_copy(item_uid, "Archive")
connector.uid_store(item_uid, "+FLAGS", [:Deleted]) <==Error occurs here
Fixed...
I needed to explicitly select the INBOX before it would allow me to make any STORE changes.
You can not rely on defaulting to the INBOX when connecting, even though it looks like you 'in' the INBOX.
connector.uid_copy(item_uid, "Archive")
connector.select("INBOX") <== Need to explicitly select the INBOX
connector.uid_store(item_uid, "+FLAGS", [:Deleted])
Thats to tricky for a screw driver monkey like me to have to work out!! :)

Rails: finding database records

In order to improve my understanding of Rails, I'm converting a Sinatra app that uses data_mapper.
I'm trying to find the best replacements for data mappers 'first' method that searches database and returns first instance of the record sought.
Can anyone comment if this is done right, or if there's a better solution?
Situation #1
Sinatra
url = Url.first(:original => original)
Rails (both of these ok?)
url = Url.find_by_original(original) #this find_by_original
url = Url.where(:first_name => 'original')
situation #2
Sinatra
raise 'Someone has already taken this custom URL, sorry' unless Link.first(:identifier => custom).nil?
My Rails (with find)
raise 'Someone has already taken this custom URL, sorry' unless Link.find(:identifier => custom).nil? #this Link.find
Original context was a method that shortens urls
def self.shorten(original, custom=nil)
url = Url.first(:original => original)
return url.link if url
link = nil
if custom
raise 'Someone has already taken this custom URL, sorry' unless Link.first(:identifier => custom).nil?
raise 'This custom URL is not allowed because of profanity' if DIRTY_WORDS.include? custom
transaction do |txn|
link = Link.new(:identifier => custom)
link.url = Url.create(:original => original)
link.save
end
else
transaction do |txn|
link = create_link(original)
end
end
return link
end
You can just use a validator on the model. Just do a validates_uniqueness_of :first_name in the Url model (app/models/url.rb). There are also other validations available in the Rails guides
EDIT
If you really want to find if such a record exists manually. You can just do Url.find(:first, :conditions => { :first_name => 'original' }) and check for nil
raise 'Someone has already taken this custom URL, sorry' unless Link.find(:first, :conditions => { :identifier => custom }).nil?
Also, if you are new to rails might want to look at the query interface. Personally, I like to use the squeel gem so my queries are strictly ruby instead of mixing ruby and sql statements.

Rails 3 - default_url_options based on url being generated

In rails 3, is it possible to gain access to the controller/action of the URL being generated inside of default_url_options()? In rails 2 you were passed a Hash of the options that were about to be passed to url_for() that you could of course alter.
E.g. Rails 2 code:
==== config/routes.rb
map.foo '/foo/:shard/:id', :controller => 'foo', :action => 'show'
==== app/controllers/application.rb
def default_url_options options = {}
options = super(options)
if options[:controller] == 'some_controller' and options[:id]
options[:shard] = options[:id].to_s[0..2]
end
options
end
==== anywhere
foo_path(:id => 12345) # => /foo/12/12345
However, in rails 3, that same code fails due to the fact that default_url_options is not passed any options hash, and I have yet to find out how to test what the controller is.
FWIW, the above "sharding" is due to when you turn caching on, if you have a large number of foo rows in your DB, then you're going to hit the inode limit on unix based systems for number of files in 1 folder at some point. The correct "fix" here is to probably alter the cache settings to store the file in the sharded path rather than shard the route completely. At the time of writing the above code though, part of me felt it was nice to always have the cached file in the same structure as the route, in case you ever wanted something outside of rails to serve the cache.
But alas, I'd still be interested in a solution for the above, purely because it's eating at me that I couldn't figure it out.
Edit: Currently I have the following which I'll have to ditch, since you lose all other named_route functionality.
==== config/routes.rb
match 'foo/:shard/:id' => 'foo#show', :as => 'original_foo'
==== app/controllers/application.rb
helpers :foo_path
def foo_path *args
opts = args.first if opts.is_a?(Array)
args = opts.merge(:shard => opts[:id].to_s[0..2]) if opts.is_a?(Hash) and opts[:id]
original_foo_path(args)
end
define a helper like
# app/helpers/foo_helper.rb
module FooHelper
def link_to name, options = {}, &block
options[:shard] = options[:id].to_s[0..1] if options[:id]
super name, options, &block
end
end
and then do the following in your view, seems to work for me
<%= link_to("my shard", id: 12345) %>
edit: or customize the foo_path as
module FooHelper
def link_to name, options = {}, &block
options[:shard] = options[:id].to_s[0..1] if options[:id]
super name, options, &block
end
def foo_path options = {}
options[:shard] = options[:id].to_s[0..1] if options[:id]
super options
end
end

What's the most efficient way to keep a user database in sync with an external mailing list service?

I'd like some advice on how I should synchronize a list of email addresses on 11k users against an external mailing list program, in this case Mailchimp.
Normally the way I'd do this is simply to have an :after_save callback, to send a single update to the external api.
But already each hour, a rake task is run to update a property on every user in the database. If I simply did that, every hour, the the poor mailchimp API would get be hit 11,000 times.
What's the most efficient, simple way to do this, to check only if a single attribute you're watching has changed from what it was before the save?
If there's a variable that persists across the transaction lifecycle I would simply do something like this, where I check if the value has changed, and if it's different execute come other code.
class User
:before_save :store_old_email
:after_save :sync_with_chimp
def store_old_email
$ugly_of_global_variable_to_store_email = user.email
end
:sync_with_chimp
if $ugly_of_global_variable_to_store_email != user.email
//update_mail_chimp_api
end
end
end
I've checked the rails api here, and I'm still slightly unclear on how I should be doing this.
Would you use the dirty? class here to do this?
This is the way I went with in the end.
It turns out Rails gives you loads of handy callbacks in the dirty to do this.
Any suggestions on how to make this code less repetitive wold be gratefully received.
def update_mailchimp(optin)
# Create a Hominid object (A wrapper to the mailchimp api), and pass in a hash from the yaml file
# telling which mailing list id to update with subscribe/unsubscribe notifications)
#hominid = Hominid.new
client_site_list_id = YAML.load(File.read(RAILS_ROOT + "/config/mailchimp.yml"))
case optin
when 'subscribe_newsletter'
logger.debug("subscribing to newsletter...")
"success!" if #hominid.subscribe(client_site_list_id['client_site_to_mailchimp_API_link'], email, {:FNAME => first_name, :LNAME => last_name}, 'html')
when 'unsubscribe_newsletter'
logger.debug("unsubscribing from newsletter...")
"success!" if #hominid.subscribe(client_site_list_id['client_site_to_mailchimp_API_link'], email, {:FNAME => first_name, :LNAME => last_name}, 'html')
when 'subscribe_monthly_update'
logger.debug("subscribing to monthly update...")
"success!" if #hominid.subscribe(client_site_list_id['monthly_update'], email, {:FNAME => first_name, :LNAME => last_name}, 'html')
when 'unsubscribe_monthly_update'
logger.debug("unsubscribing from monthly update...")
"success!" if #hominid.unsubscribe(client_site_list_id['monthly_update'], email, {:FNAME => first_name, :LNAME => last_name}, 'html')
end
end
# Keep the users in sync with mailchimp's own records - by only firing requests to the API if details on a user have changed after saving.
def check_against_mailchimp
logger.info("Checking if changes need to be sent to mailchimp...")
if newsletter_changed?
logger.info("Newsletter changed...")
newsletter ? update_mailchimp('subscribe_newsletter') : update_mailchimp('unsubscribe_newsletter')
end
if monthly_update_changed?
logger.info("update preferences changed...")
monthly_update ? update_mailchimp('subscribe_monthly_update') : update_mailchimp('unsubscribe_monthly_update')
end
end
you could change your users model to an active resource instead of active record and just use mailchimps api as your db for users
this is an older post about active resource but might get you started down the right path
http://www.therailsway.com/2007/9/3/using-activeresource-to-consume-web-services

Resources