GithubHelp home page GithubHelp logo

arcath / adauth Goto Github PK

View Code? Open in Web Editor NEW
174.0 14.0 29.0 376 KB

A Ruby interface for Microsoft's Active Directory based off ruby-net-ldap

Home Page: http://adauth.arcath.net

License: MIT License

Ruby 95.77% HTML 4.23%

adauth's Introduction

Adauth

RDoc | www | Gempage | Status | Code Climate | Dependency Status

Easy to use Active Directory Authentication for Rails.

Install

Add the Adauth gem to your Gemfile:

gem 'adauth'

and run a bundle install

Usage

In Rails

First off create a new config file by running the config generator

rails g adauth:config

Fill out the config values in config/initializers/adauth.rb

Joining a model to Adauth

If you want to link your user model to Adauth you can use this simple code:

class User < ActiveRecord::Base
	include Adauth::Rails::ModelBridge
	
	AdauthMappings = {
		:login => :login
		:group_strings => :cn_groups
	}
	
	AdauthSearchField = [:login, :login]
end

This gives you a bridge between Adauth and your model. When you call User.create_from_adauth(adauth_model) it does:

u = User.new
u.login = adauth_model.login
u.group_strings = adauth_model.cn_groups
u.save

This can be used for any model and anything that you pull over through adauth.

SessionsController

You can use a premade sessions controller by running

rails g adauth:sessions

Which adds a couple of routes, a sessions controller and a login form. To login go to /sessions/new and fill out the form, you will then POST to /adauth and if succesful you will be sent back to root_path

In Scripts

To use Adauth in a script or other program just call Adauth.configure somewhere at the begining of the script, once configured Adauth can be used anywhere in your program the same as rails.

Configuring

Adauth has a few configuration options which are described in detail on the wiki.

Logs

Adauth logs to weekly logs in logs/adauth.log(.DATE)

You can interact with the logger through Adauth.logger and set a new one using Adauth.logger=

Developing

Before you can run the tests you will need to write a yml file with your domain settings in and place it at spec/test_data.yml, there is an example of this file in the spec folder.

When you fork Adauth please:

  1. Create your feature branch (git checkout -b my-new-feature)
  2. Commit your changes (git commit -am 'Add some feature')
  3. Push to the branch (git push origin my-new-feature)
  4. Create new Pull Request

adauth's People

Contributors

arcath avatar coreypurcell avatar henningms avatar jekhokie avatar jerry avatar mjblack avatar plukevdh avatar tcfunk avatar tedpennings avatar vollnhals avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

adauth's Issues

Groups with over 1500 members...

I'm having a problem with groups over 1500 members... Apparently they only return the first 1500 members and as a different field leaving the member array empty, which causes problems..

:member=>[], :"member;range=0-1499"=>[blah blah blah]

Any idea how to get around this?

simple_authenticate still requires a query user

As I understand the purpose of simple_authenticate is authenticating a user against the domain without requiring a query user. This is not happening, as per sequence below:

  1. Adauth::AdObjects::User.simple_authenticate(usr, pwd)
  2. allowed_to_login
  3. usr.cn_goups_nested
  4. Adauth::AdObjects::Group.where('name', group)
  5. filter
  6. Adauth.connection
  7. _connect_
  8. Adauth::Connection.new(connection_hash(_@config.query_user, @config.query_password_))

This happens because Adauth.connection doesn't return a connection, despite there's an existing one
created by Adauth::AdObjects::User.authenticate to authenticate the user against the AD; Adauth.connect therefore tries to establish a new connection with the query user credentials, which will be empty, resulting in a <Net::LDAP::LdapError: invalid binding information> error.

The tests themselves simple_authenticate the query user, which kind of defeats the whole purpose of it.

Imho the simplest approach to solve this would be to change Adauth.connection_hash in order to use provided credentials if no query user is configured:

  • accept empty user and password arguments by default (user = "", password = "")

  • use those values as fallback when no query user is configured, in the likes of

    :username => @config.query_user.blank? ? user : @config.query_user,
    :password => @config.query_user.blank? ? password : @config.password

In this line, would also need to change:

  • Adauth.connection, in order to accept empty user and password arguments by default (user = "", password = "")
  • ditto for Adauth.connect

Deny_groups

Aswell as allowed_groups which acts like a white list of groups allowed to login there should be "deny_groups" which is a black list of groups which aren't allowed to login.

Configuration examples in wiki

Some configuration examples should be provided in the wiki.

Examples I think are needed

  • Standard, with the DC on the same network
  • Externally hosted passing through a firewall
  • Only allowing groups X and Y
  • Limit to a specific OU

allowed_groups error

There's a boundary case that isn't handled cleanly, which is when allowed_groups is configured but the user attempting to authenticate isn't defined in any specific AD groups at all. In this case 'memberof' isn't defined in the returned Net::LDAP::Entry object. I know little about AD administration so I'm not sure how likely this is in practice, but I just hit it while testing against an internal dev AD instance.

Ideally of course, the behavior should be the same -- if you're not a member of any groups, then you're not a member of the allowed group(s) and authentication is denied.

OU Filtering without altering the base string

Altering the base string to do OU filtering is a bit constricting as a structure of

Base
  IT
  Dev
  Mangement

would only allow you to limit to one of those OUs, if you wanted to limit to both IT and Dev you would need to change your structure or add some security groups. It would be a lot better if there was a multi value attribute that returned an array of the OUs a user is a member of allowing for the same filtering as groups

passwordless login!

We've noticed that it's possible to login using adauth by supplying a correct username & a blank password. We've seen the following:

correct username & correct password -> login
correct username & incorrect password -> fail
correct username, & blank password -> login (as username)
incorrect username & [correct|incorrect|blank] password -> fail

This is even for users who are NOT currently in the users table (ie have never previously logged in).

Currently using the latest git master branch (pulled from git today using bundle update):
https://github.com/Arcath/Adauth.git

Setup is like this (boring bits omitted):

initializers/adauth.rb
c.doman = "domain.local"
c.query_user = "<a low privilege account>"
c.query_password = "****"
c.server = "10.0.0.1"
c.base = "dc=domain, dc=local"
models/user.rb
class User < ActiveRecord::Base

include Adauth::Rails::ModelBridge

AdauthMappings = {
    :login => :login,
    :group_strings => :cn_groups,
    :ou_strings => :dn_ous,
    :email => :email,
    :name => :name
}

AdauthSearchField = [:login, :login]
controllers/sessions_controller.rb

def create
   begin
        ldap_user = Adauth.authenticate(params[:username], params[:password])
        if ldap_user
            user = User.return_and_create_from_adauth(ldap_user)
            session[:user_id] = user.id
            redirect_to <somewhere nice> and return
        else
            redirect_to <a bad place> and return
        end
    rescue
        redirect_to <an even worse place> and return
    end
end

def new
    redirect_to root_path if current_user
end

We added the begin/rescue/end in the create session because we encountered an error when giving incorrect usernames/passwords (ldap_user was true even for bad usernames - like logging in as asdf/crap_pass)

Removing the rescue still lets you log in without a password, and errors if the username or password is incorrect (but not blank), ie it being there has no effect on this issue, but it does prevent an error.

What's going on?

Oh, an excerpt from the users table:

id: 18
login: james
group_strings: --- - !ruby/string:Net::BER::BerIdentifiedString |-   U1NMIFZQTiBVc2Vycw== - !ruby/string:Net::BER::BerIdentifiedString |-   QXBwX1NoYXJlcG9pbnRfS25vd2xlZGdlVXBkYXRlcnM= - !ruby/string:Net::BER::BerIdentifiedString |-   RGVwdF9JVA== - !ruby/string:Net::BER::BerIdentifiedString |-   RXZlcnlvbmUgT3V0bG9vaw== - !ruby/string:Net::BER::BerIdentifiedString |-   RG9tYWluIEFkbWlucw== - !ruby/string:Net::BER::BerIdentifiedString |-   RW50ZXJwcmlzZSBBZG1pbnM= 
name: James
ou_strings: --- - Users - IT - Department - CompanyHO 
email: [email protected]

Not sure whats going on with group_strings(!) or ou_strings, but I don't currently use them for anything.
This user logged on by providing username "james" and no password

Define additional attributes to include in local User model?

How do I include other AD fields when creating a local User? For example, I want to grab a user's email address and save it so I can send emails, but it looks like return_and_create_with_adauth only grabs and creates a record with login, group_strings, ou_strings, and name

Error in documentation?

I'm not sure, but I think that there is an error in the documentation at https://github.com/Arcath/Adauth

The code on the page was

class User < ActiveRecord::Base
include Adauth::Rails::ModelBridge

AdauthMappings = {
    :login => :login
    :group_strings => :cn_groups
}

AdauthSearchField = [:login, :login]
end

The code I used after getting an error was

AdauthMappings = {
:login => :login,
:group_strings => :cn_groups
}

AdauthSearchField = [:login, :login]

I had to put a comma after login.

If the posted code is correct, I apologize.

Save additional fields in the database

Is it possible to configure this gem to store additional fields in the database, such as the user's email address?

I have tried looking around for an answer to this question but haven't been able to find an answer.

Change password

Now that Adauth 2.0.0 can find out anything about active directory and modify fields it makes sense for it to be able to reset paswords.

This isn't as simple as performing a replace on password as it needs to be hashed and supplied in an odd way I havent worked out yet. Once i do it will most likely end up in Adauth::AdObjects::User.reset_password()

Catch Timeouts

At the moment if the LDAP server is unavliable Adauth takes 20-30 seconds to return an invalid login, would be alot better if it caught the error after 10 seconds and returned a message asking the end user to contact IT support (all changable of course)

Tests shouldn't rely on a real AD instance

@Arcath, I have a suggestion and wanted to run it by you before I invest any time in this. When working on specs I generally prefer to mock out external systems as a) it's too much of a hassle to work with a real AD, b) I don't want to screw up and run tests against a real AD system, and c) it's hard to fake issues with that ext. system.

Have you thought about mocking the Net::LDAP call(s), and replacing them with fakes? This could get to be a hassle to do for the tests, but it may not. I can spend a few mins looking into it, but if you're against the idea and would reject the pull request, I won't.

Thanks for providing the gem, I tested it out in a script and it looks good. jz

Edit login form

I ran the following command
rails g adauth:sessions
Which as the documentation states,

adds a couple of routes, a sessions controller and a login form

How do I change the style of the login form? I can't find where it's located

Please provide meaningful error message if user is not found

In lib/adauth/authenticate.rb the code below is going to result in a “crash” when the search result is empty.

            user = Adauth::AdObjects::User.where('sAMAccountName', username).first
            if allowed_to_login(user)
                Adauth.logger.info("authentication") { "Authentication succesful" }
                return user
            else
                Adauth.logger.info("authentication") { "Authentication failed (not in allowed group or ou)" }
                return false
            end

Calling the method first on the empty result, results in user being nil, which causes failures further down the path. Could a check be added, so that false is returned if user is nil.

Even better would be to return more error codes or even messages, which can then be displayed in the log-in dialog.

Initilaizer Generator

There should be a generator that creates a basic version of the initializer with "example.com" and descriptive comments.

Tidy up tests

Although Adauth has tests for all its code they aren't laid out very well and a lot of advanced functionality is tested in the same file as making sure we can fetch a user.

Before 2.1.0 I would like to move them all around and seperate them out into meaningfully named files and folders

Federated Active Directory Server Integration

My company has federated its on-premises Active Directory server with Microsoft Office 365 in the cloud. Are there any configuration considerations to take into account when trying to authenticate against it? Is it even supported?

ruby 1.9 adding [" "] around name and login

When using ruby 1.8 the data is presented correctly. When using ruby 1.9 I am seeing this type of behavior

User id: 2, name: "["Matthew Black"]", login: "["mblack"]", first_name: nil, last_name: nil, email: nil, groups: nil

Where as ruby 1.8 it will be like this
User id: 2, name: "Matthew Black", login: "mblack", first_name: nil, last_name: nil, email: nil, groups: nil

Can only use 1 of the 4 filter arrays

At the moment it is not possible to filter on multiple items eg. allowed_groups and denied_ous. You basically have to fit it all into one of the arrays.

while running rails g adauth:all

i get the response

extra Add this code to your ApplicationController

          helper_method :current_user

          def current_user
              @current_user ||= User.find(session[:user_id]) if session[:user_id]
          end

and then it stops

any resolutions?

Users with large numbers of group memberships experience slow login times using TLS

There is an issue where if a user is associated with a large number of groups the time required to login when simple_tls is enabled is greatly increased. The average response time for a user with 100 group memberships is around 1 second when simple_tls is not in use. However, when it is in use the time escalates to 20+ seconds for a login.

I believe this is caused by the amount of repeated queries that is done combined with the overhead of simple_tls.

For each group membership there are an additional four queries -- two group queries and two user queries. A user with 100 group memberships generate 500 queries. The interesting thing is that the additional four queries seem irrelevant, the first query is against a null user/group. The second set of queries is against a seemingly random user that is a member of the group.

Searching for all "(objectClass=user)" where sAMAccountName = user_a
Connecting to AD as "ad_user"
Searching for all "(objectClass=group)" where name = group_a
Searching for all "(objectClass=user)" where sAMAccountName = 
Searching for all "(objectClass=group)" where sAMAccountName = 
Searching for all "(objectClass=user)" where sAMAccountName = user_b
Searching for all "(objectClass=group)" where sAMAccountName = user_b
Searching for all "(objectClass=group)" where name = group_b
Searching for all "(objectClass=user)" where sAMAccountName = 
Searching for all "(objectClass=group)" where sAMAccountName = 
Searching for all "(objectClass=user)" where sAMAccountName = user_c
Searching for all "(objectClass=group)" where sAMAccountName = user_c
...
(500 lines)

Encoding Issue

As described in ruby-ldap/ruby-net-ldap#44 this problem has been fixed in a fork of net-ldap.

I will be thinking about how to move adauth forward with one of its dependencies seemingly abandoned.

Guess c.base

Once inssue #12 is sorted there isn't any real need for the developer to have to workout the LDAP base string. It would be relatively easy to work it out off c.domain.

Cannot connect to Windows 2000 Server with ruby-net-ldap

Hey there,

I can't get ruby-net-ldap (latest version I get is 0.0.4) to connect to my legacy Windows 2000 Server's AD, however net-ldap (0.2.2 here) works fine.

Is there a specific reason why the former is a dependency of your gem, and not the latter?

As it is, your gem just won't work for me :-/

Best,

Allow for custom return data

At the moment Adauth only fetches the information it needs to authenticate and create the user record. It would be better if a developer could set a config option that returned additional details such as:

c.ad_attrs = ["email", "phone_number", ...]

So their application can send out emails etc...

Strange issue with group_strings (Adauth v 2.0.1 & 2.0.2)

I've been having an odd issue with adauth in production vs development. There's a bit of background so I'll try and explain.

When running in dev, there are no issues signing in to the app at all - it works fine.
However, when running in production some users can only sign in the first time they ever use it - subsequent sign-ins fail. Specifically, they can only sign in if there is no user record for them in the users database table.

The background:
We had an issue a while back (I think I mentioned it here) where there was an unrelated error (uncaught exception I'd call it). We worked around it by wrapping the code in the sessions_controller in a try catch block. This worked fine, but it ended up masking this problem. Having removed the try/catch I can now see exactly what the issue is.

When the user signs in the first time, a record is created for them in the users table. For some reason group_strings is being set to this (or similar depending on the user):

"---
- !ruby/string:Net::BER::BerIdentifiedString |-
  U1NMIFZQTiBVc2Vycw==
- !ruby/string:Net::BER::BerIdentifiedString |-
  QXBwX1NoYXJlcG9pbnRfS25vd2xlZGdlVXBkYXRlcnM=
- !ruby/string:Net::BER::BerIdentifiedString |-
  RGVwdF9JVA==
- !ruby/string:Net::BER::BerIdentifiedString |-
  RXZlcnlvbmUgT3V0bG9vaw==
- !ruby/string:Net::BER::BerIdentifiedString |-
  RG9tYWluIEFkbWlucw==
- !ruby/string:Net::BER::BerIdentifiedString |-
  RW50ZXJwcmlzZSBBZG1pbnM=''

This doesn't look right at all. ou_strings is set to something reasonable though:

"---
- Users
- IT
- Department
- HO"

The problem occurs on subsequent logins, where we now get an internal server error (now we've removed the try/catch anyway):

ActiveRecord::StatementInvalid (TinyTds::Error: String or binary data would be truncated.: EXEC sp_executesql N'UPDATE [users] SET [group_strings] = ''<that load of string up above>

So the column is a varchar(255) and the group_strings is too large to fit, so an error is thrown.

For now, I am increasing the column size, but I'd love to know why the group_strings looks so odd.

I've tried 2.0.1 and 2.0.2 as well as pulling from git master, all the same.

undefined method split for nil - ad_objects/group.rb line 51

I've been using AdAuth in a couple apps for awhile and recently upgraded one to 2.0.3 and most of my users are fine, but for one particular user I'm getting an error in the above mentioned line about not being able to split nil. The relevant part of the stack trace:

adauth (2.0.3) lib/adauth/ad_objects/group.rb:51:in cn_groups' adauth (2.0.3) lib/adauth/ad_object.rb:111:inblock in cn_groups_nested'
adauth (2.0.3) lib/adauth/ad_object.rb:109:in each' adauth (2.0.3) lib/adauth/ad_object.rb:109:incn_groups_nested'
adauth (2.0.3) lib/adauth/authenticate.rb:26:in allowed_to_login' adauth (2.0.3) lib/adauth/authenticate.rb:10:inauthenticate'
app/controllers/sessions_controller.rb:8:in `create'

I've checked and the user is pretty much in the same groups as another user with similar groups. I assume that there's something misconfigured in her AD account, I just can't seem to figure out what.

Causes error with Rails 3.1

When using rails 3.1 rails s returns this error when adauth is in the gemfile

 asset_paths.rb:66: uninitialized constant ActionView::Helpers::AssetTagHelper::AssetPaths::Mutex (NameError)

License missing from gemspec

Some companies will only use gems with a certain license.
The canonical and easy way to check is via the gemspec
via e.g.

spec.license = 'MIT'
# or
spec.licenses = ['MIT', 'GPL-2']

There is even a License Finder to help companies ensure all gems they use
meet their licensing needs. This tool depends on license information being available in the gemspec.
Including a license in your gemspec is a good practice, in any case.

How did I find you?

I'm using a script to collect stats on gems, originally looking for download data, but decided to collect licenses too,
and make issues for missing ones as a public service :)
https://gist.github.com/bf4/5952053#file-license_issue-rb-L13 So far it's going pretty well

`allowed_ous` ignored

Despite having the following in config/initializers/adauth.rb

    c.allowed_ous= ["OU1", "OU2"]

members from other organizational units are matched too.

Is there a way to test if that is an AD configuration error or an Adauth error?

Authenticating without Query_user

I am updating an app that I built about two years ago with this gem. All I need to do is authenticate a password against a username. This was easy before (no query_user needed). I don't want to ask the IT department to create a system user that I will have to hard code in my app (when they ask me why I need one now when I don't need one for the old version I don't have a good answer).

So why is query_user required now and is there a way around this?

Possible to re-validate password?

I'll admit I haven't tried doing this, not entirely sure how to begin.

Is it possible to re-validate an already signed in users' password?

Example:
A user has signed in successfully. Now they want to visit a more sensitive area of the site, I would like them to re-type their password again before allowing them to proceed.

Logging

It would be helpful if the conversation adauth has with the AD sever could be logged, perhaps a config option to turn it on or off (per environment) which would get adauth to output some useful debugging to the log/console

objectguid parameter malformed data

When retrieving objectguid the data is modified.

This happens in handle_field at

return (@ldap_object.send(field)to_s).gsub(/\"|\[|\]/, "")

by changing it to

return @ldap_object.send(field).first.to_s

it fixes my problem however i use very few fields.

Reset password instead of change?

I see how to change a password. Person needs to be authenticated? We're trying to setup a reset password where the person does not know his/her password and is not logged in. Thanks for any info!

Provide full MVC support

Instead of the having the developer write the code for the views, controllers and models Adauth should provide defaults that can be overridden. This would mean providing the current_uesr method and the SessionsController in Adauth aswell as the generators to create the correct files.

I don't think the functionality of the controllers should be provided by middleware as it will be a lot easier for developers to change the functionality if they just change a generated controller, instead of having to over ride middleware.

Remember Me - possible?

Hi,

I was wondering whether it would be possible to make adauth do some kind of "remember me" thing?

I'd like to be able to let our users log in & then be auto-logged in next time (for some set period of time), kinda like how devise allows you to do. Currently the session cookie that is set expires on browser close (so they are asked to log in next time).

I don't know whether this is something that adauth could/should handle, or whether it is something I would need to do in my app specifically (if so, have you any pointers on how?)

Thanks,
James

Adding a secondary domain server?

Is there a way to add a secondary domain server to the adauth.rb file?

i.e. in the lines :

 # The IP address or Hostname of a DC (Domain Controller) on your network

This could be anything and probably wont be 127.0.0.1

Again contact your IT Support if you can't work this out

c.server = "10.0.0.25"

so that I could do something like

c.server = "10.0.0.25", "10.0.0.26"

If this is a current capability, how do you do it? If not, can it be added?

thanks

Pickup on current logged in user

As bought up on reddit by mikefh.

When pitching a Rails solution for intranet projects, I've regularly hit the same hurtles... some person always pitches SharePoint because of the ability to "pickup on the user currently logged into the Windows PC." I was hoping that Adauth could assist this exact point.

I'm not really sure how this could work but its worth looking to see if there is someway Adauth can be used to provide this functionality.

My current line of thinking is that share point must use some kind of javascript or activex object to pickup on the user via some kind of token that can't be forged, so if that could be passed to adauth somehow then it maybe possible to find the current user.

I'm not assigning this to 1.1.0 (yet) until I have some idea how to achieve it but any input on this issue would be greatly appreciated.

Login a user without their password

If for what ever reason you simply want to log a user in without checking credentials (possibly a guest user) to keep the details you will need to be bale to run the normal authentication process without the password for that user.

This is part of the solution to issue #10

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.