checking groups at runtime with devise and devise_ldap_authenticatable - ruby

I can get this devise_ldap_authenticatable working just fine when I don't care about what groups they are, it either connects to ldap and authenticates the user signing in under devise or doesn't. But I want to let only certain members that are apart of one or several specific groups in. I had a post on this question here:
Checking group membership in rails devise ldap gem, is it in the yaml?
(the gem for completeness sake is this one: https://github.com/cschiewek/devise_ldap_authenticatable)
Got to thinking I am asking the wrong question. I think I want to know how in devise (and the devise_ldap_authenticatable is the data stored where perhaps I can peek at my array of memberOf's myself and check the groups for myself in code, and then at that time don't let them in. Is there anywhere on the net that's hows this? My googling has turned up nothing but not being a ldap or devise pro I am guessing my terms suck.
I am sure I just might of missed the how to do this, closest I can see that might help (Though in its form as I read it makes little sense to me is the part on the readme here:
https://github.com/cschiewek/devise_ldap_authenticatable/blob/master/README.md
about querying ldap, is this the case?)

You could do this with a callback or validation on the User (or equivalent) model.
before_create :user_is_not_member_of_specified_group?
private
def user_is_not_member_of_specified_group?
member_of = Devise::LdapAdapter.get_ldap_param(self.username,"memberOf")
test member_of
end
where test is a method that returns true/false based on your conditions for the member groups.
The Devise::LdapAdapter.get_ldap_param(self.username,"memberOf") is a method from devise_ldap_authenticatable that will return an array of member groups. You'll want to run your group testing on this array.
If you use a validation you could specify an error message for users that failed the test. Hope this helps.
EDIT
Another way to handle this would be to let your gem handle the redirection and error messages by monkeypatching the authorized? method in Devise::LdapAdapter::LdapConnect (https://github.com/cschiewek/devise_ldap_authenticatable/blob/master/lib/devise_ldap_authenticatable/ldap_adapter.rb). It would look like:
Devise::LdapAdapter::LdapConnect.class_eval do
def user_group_test
member_of = self.ldap_param_value("memberOf")
test member_of # your group test method
end
def authorized?
DeviseLdapAuthenticatable::Logger.send("Authorizing user #{dn}")
if !user_group_test
DeviseLdapAuthenticatable::Logger.send("Not authorized because custom authentication failed.")
return false
elsif !authenticated?
DeviseLdapAuthenticatable::Logger.send("Not authorized because not authenticated.")
return false
elsif !in_required_groups?
DeviseLdapAuthenticatable::Logger.send("Not authorized because not in required groups.")
return false
elsif !has_required_attribute?
DeviseLdapAuthenticatable::Logger.send("Not authorized because does not have required attribute.")
return false
else
return true
end
end
end
You would want to put this in a custom initializer file in config/initializers.

Related

With the state_machine gem, can I can I perform a transition without saving?

I'm working on an app that uses state_machine. I want to call a transition method but not persist the change, so that I have an opportunity to check whether the proposed change is authorized. Eg:
def some_controller_action
# ...
account.close # but don't save...
authorize account # will explode if current_user may not do this
if account.save ....
How can I do this?
Pass false
Eg:
account.close(false) # does not save
This isn't exactly documented, but I found it here.

Validation & External APIs - Rescue in Controller or Fatten Model validations?

I am using Restforce to query records from a remote salesforce instance. The user simply has to put in a valid UID for the record they want to query.
Restforce uses Faraday middleware to deal with http requests - and raises a Faraday::ResourceNotFound error if I request something that cannot be located in the remote salesforce database.
Question
Where should I validate user input?
I have two ideas but i'm not sure of the consequences to each... and I'm trying to work out how to stick best to the fat model - skinny controller best practice.
Check for successful query at the application controller level
Requests save the UID to a simple ActiveRecord model #record_request. My create method can fire a query, check for an error and flash/redirect the user if needed.
# app/controllers/record_requests_controller
def create
#record_request = current_user.record_requests.new(record_request_params)
# Check to see if CHAIN number exists
if #record_request.restforce.find("Contact", #record_request.chain_number, 'ClientID__c')
# If it gets past that do standard validation checks
if #record_request.save
flash[:success] = 'Record request was successfully created.'
redirect_to record_requests_path
else
render :new
end
end
end
Then over in the ApplicationController I've got a rescue method setup
# app/controllers/application_controller
rescue_from Faraday::ResourceNotFound, with: :resource_not_found
private
def resource_not_found
flash[:alert] = 'Cannot find resource on Salesforce'
redirect_to(:back)
end
This works! And seems fine... but...
Model level validation?
My gut tells me this is a validation and should be validated on the model level, what if there's a bug and something sneaks into my database? Should this all just be checked at the if #record_request.save moment?
If so... how would i get model level code to handle validation AND be able to fire off an external (OAuth authenticated) API request without breaking the MVC.
What are the implications to either, and how might I do better?
I think the best way to use model level validation something like this:
validate :something
def something
errors.add(:field, 'error message') unless RestClient.check_something
end
Where RestClient is singleton object in /lib folder. This will allow to keep controller clean.

How to access Google Contacts API in ruby

I'm struggling to access the Google Contacts API.First I tried the google-api-ruby-client gem but it turned out that it does not support the Contacts API.
Next shot was the google_contacts_api gem. I used oauth2 to access the authentication key(Getting authentication token guide question). But after passing the token correctly to the api it is producing an error.
`<main>': undefined method `[]' for #<GoogleContactsApi::GroupSet:0x000000039fcad8>` (NoMethodError).
Here is my code.
# get token using oauth2 gem, and use it below in the google_contacts_api.
google_contacts_user = GoogleContactsApi::User.new(token)
contacts = google_contacts_user.contacts
groups = google_contacts_user.groups
# group methods
group = groups[0]
group.contacts
puts group.contacts
# contact methods
puts contacts.count
puts groups.count
contact = contacts[0]
contact.primary_email
contact.emails
What am I doing wrong?
UPDATE:
As #alvin suggested it is working now. But the group contacts are not being printed out. Instead it is printing #<GoogleContactsApi::ContactSet:0x000000020e49d8>.Example: here is what is printed by this code
groups = google_contacts_user.groups
# group methods
groups.each do |group|
group_contacts = group.contacts
puts group_contacts
end
Output:
#<GoogleContactsApi::ContactSet:0x000000020e49d8>
#<GoogleContactsApi::ContactSet:0x0000000504aec0>
#<GoogleContactsApi::ContactSet:0x0000000518dfd0>
#<GoogleContactsApi::ContactSet:0x000000052d9290>
#<GoogleContactsApi::ContactSet:0x000000054280d8>
#<GoogleContactsApi::ContactSet:0x0000000558c2f8>
#<GoogleContactsApi::ContactSet:0x00000005746eb8>
#<GoogleContactsApi::ContactSet:0x000000058a3ea0>
How can I print the group contacts?
Edited to add info about the Enumerable implementation
(I wrote the gem.)
There was a bug in the documentation. groups and contacts are instances of classes that implement Enumerable, which doesn't provide the [] method, but does provide the first method.
So, try groups.first instead of groups[0]. Likewise, use contacts.first instead of contacts[0]. My bad! (I probably did a to_a in my head.)
Response to Update
To answer the second half of the question, it looks like you found the relevant convenience methods for Contact and Group, in particular the Contact.primary_email method. See more methods in the (somewhat incomplete, sorry) YARD docs.
To get all the emails, you basically need to iterate over the returned contacts. As I mentioned in the updated response to the first part of your question, groups and contacts have all the methods of Enumerable. (Enumerable documentation). Here are some examples:
# What are all the groups called?
user.groups.map(&:title)
# Find group by title. (Returns nil if no such group.)
group = user.groups.select { |g| g.title = "Group Name" }
# Get all primary emails from a group
group.contacts.map(&:primary_email)
# Get all primary emails from all contacts regardless of group
user.contacts.map(&:primary_email)
You only need to use the Hashie::Mash methods to access data when no convenience accessor is provided (for example, if Google starts returning extra data the gem hasn't accounted for yet). The use case you described doesn't require this.
P.S. In the future, you might want to open a new question instead of editing your existing question.

Rspec and FactoryGirl Uniqueness Failing

I'll be brief with the code samples, as all of my tests pass except the one below. I got it to pass by changing things up a bit, but I'm not sure why version 1 fails and version 2 works.
My model:
# app/models/person.rb
class Person
validates :contact_number, uniqueness: true
end
Model spec
# spec/models/person_spec.rb
require 'spec_helper'
describe Person do
it 'is a valid factory' do
create(:person).should be_valid # passes
end
it 'has a unique phone number' do
create(:person)
build(:person).should_not be_valid # fails
end
it 'also has a unique phone number' do
person1 = create(:person)
person2 = person1.dup
person2.should_not be_valid # passes
end
end
As far as I can tell, the two uniqueness tests should be doing the same thing, however one passes and one fails.
If it matters, I am using mongoid, though I don't think that should have any effect. I'm also not doing anything with nested contexts or describes in my test, so I think the scope is correct. Any insight is appreciated.
UPDATE 1: I realized in my factory I am adding an initialize_with block like this:
initialize_with { Person.find_or_create_by(contact_number: contact_number) }
I realized that this may be the reason the validation was failing -- I was just getting the same person back. However, commenting out that line gives the following error:
Mongoid::Errors::Validations:
Problem:
Validation of Person failed.
Summary:
The following errors were found: Contact number is already taken
Resolution:
Try persisting the document with valid data or remove the validations.
Which, in theory is good, I suppose, since it won't let me save a second person with the same contact number, but I'd prefer my test to pass.
Probably your person factory has a sequence in contact_number making a diferent contact_number in each person.
Just realize that the build(:person) doesnt validate. The validation occurs only in create.
I strongly suggest use of shoulda-matchers for this kind of validations.
It is possible that your database is being cleaned (do you have database-cleaner in your Gemfile), or your tests are not being run in the order you think they are. (Check for :random in your spec_helper.rb)
While the above answer regarding using shoulda-matchers will help you run this particular test in RSpec more concisely, you probably want to have your unique phone number test be able to be run completely on its own without relying on another spec having executed. Your second test is an example of Obscure Test (and also a little bit of Mystery Guest http://robots.thoughtbot.com/mystery-guest), where it's not clear from the test code what is actually being tested. Your phone number parameter is defined in another file (the factory), and the prior data setup is being run in another spec somewhere else in the file.
Your second test is already better because it is more explicitly showing what you're testing, and doesn't rely on another spec having been run. I would actually write it like this to make it more explicit:
it 'has a unique phone number' do
person1 = create(:person, phone_number: '555-123-4567')
person2 = create(:person, phone_number: '555-123-4567')
# can use 'should' here instead
expect(person2).not_to be_valid
end
If you don't explicitly make it about the phone number, then if you change your factory this test might start failing even though your code is still sound. In addition, if you have other attributes for which you are validating uniqueness, your previous test might pass even though the phone number validation is missing.
I figured it out! On a whim, I checked out the test database and noticed that a Person object was lingering around. So, it actually wasn't the
build(:person).should_not be_valid that was raising the Mongoid exception. It was the create call on the line before. Clearing out the DB and running the spec again passed, but again the data was persisting. I double checked my spec_helper.rb file and noticed I wasn't calling start on the DatabaseCleaner. My updated spec_helper.rb file looks like this, and now everything works:
# Clean up the database
require 'database_cleaner'
config.mock_with :rspec
config.before(:suite) do
DatabaseCleaner.strategy = :truncation
DatabaseCleaner.orm = "mongoid"
end
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end

Devise: Is it possible to NOT send a confirmation email in specific cases ? (even when confirmable is active)

Here is my situation, I use devise to allow users to create account on
my site and manage their authentication.
During the registration process I allow customers to change some
options, leading to an actually different account being created but
still based on the same core user resource.
I would like to choose not to send a confirmation email for some of
those account types. I don't care if the account do not get confirmed
and user cannot log in, that's ok, no pb with that.
How would I go about doing that ?
Thanks,
Alex
Actually it's quite easy once I dig a little deeper.
Just override one method in your User model (or whatever you are using):
# Callback to overwrite if confirmation is required or not.
def confirmation_required?
!confirmed?
end
Put your conditions and job's done !
Alex
If you just want to skip sending the email but not doing confirmation, use:
# Skips sending the confirmation/reconfirmation notification email after_create/after_update. Unlike
# #skip_confirmation!, record still requires confirmation.
#user.skip_confirmation_notification!
If you don't want to call this in your model with a callback overwrite this method:
def send_confirmation_notification?
false
end
You can also simply add the following line of code in your controller before creating the new user:
#user.skip_confirmation!
I don't know if Devise added this after the other answers were submitted, but the code for this is right there in confirmable.rb:
# If you don't want confirmation to be sent on create, neither a code
# to be generated, call skip_confirmation!
def skip_confirmation!
self.confirmed_at = Time.now
end
I was able to do something similar with the functions:
registrations_controller.rb
def build_resource(*args)
super
if session[:omniauth] # TODO -- what about the case where they have a session, but are not logged in?
#user.apply_omniauth(session[:omniauth])
#user.mark_as_confirmed # we don't need to confirm the account if they are using external authentication
# #user.valid?
end
end
And then in my user model:
user.rb
def mark_as_confirmed
self.confirmation_token = nil
self.confirmed_at = Time.now
end

Resources