ons-ldap 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b2d412ce6348a1a3eefe1ff3bc96a1fac4f980f9
4
+ data.tar.gz: d77ef02808888a498f15a5970861faac220e958e
5
+ SHA512:
6
+ metadata.gz: 811b492711c0b3a4cb97a54717fc2ad486daf1acbc33f65cfc7436a56aad8943063ec9df5d27b6546d119d44b761f9124256115ae75bd14df17f50b9ad1472c2
7
+ data.tar.gz: 43fb9137d557753022d7203e4314fccf2ad1b5aef4a9de4a621f5fce4f360deba45ea2076f72b31e2e3337c1c408b67eebe5bc21965851a7bd17ec4797ff348c
@@ -0,0 +1,38 @@
1
+ # ONS LDAP RubyGem
2
+ Thin wrapper around the [net-ldap](https://rubygems.org/gems/net-ldap) RubyGem. Contains a simple `LDAPConnection` class that can be used to authenticate against an LDAP directory.
3
+
4
+ ## Installation
5
+
6
+ ```
7
+ gem install ons-ldap
8
+ ```
9
+
10
+ ## Example
11
+
12
+ ```ruby
13
+ require 'ons-ldap'
14
+
15
+ host = 'localhost ' # LDAP server host
16
+ port = '398' # LDAP server port
17
+ base = 'dc=example,dc=com' # LDAP tree base
18
+
19
+ # Hash of LDAP group names.
20
+ groups = { admins: 'admins', users: 'users' }
21
+
22
+ ldap_connection = LDAPConnection.new(host, port, base, groups, logger)
23
+ user_entry = ldap_connection.authenticate('johntopley', 'password')
24
+
25
+ user_entry.user_id #=> 'johntopley'
26
+ user_entry.display_name #=> 'John Topley'
27
+ user_entry.token # 2FA token, stored in LDAP's employeeNumber field for expediency
28
+ user_entry.groups #=> ['admins', 'users']
29
+ ```
30
+
31
+ ## Testing
32
+
33
+ ```
34
+ rake test
35
+ ```
36
+
37
+ ## Copyright
38
+ Copyright (C) 2016 Crown Copyright (Office for National Statistics)
@@ -0,0 +1 @@
1
+ require_relative 'ons-ldap/ldap_connection'
@@ -0,0 +1,106 @@
1
+ require 'net/ldap'
2
+
3
+ UserEntry = Struct.new(:user_id, :display_name, :token, :groups)
4
+
5
+ class LDAPConnection
6
+
7
+ # Class instance variables.
8
+ class << self
9
+ attr_accessor :host
10
+ attr_accessor :port
11
+ attr_accessor :base
12
+ attr_accessor :groups
13
+ attr_accessor :logger
14
+ end
15
+
16
+ def initialize(host, port, base, groups, logger)
17
+ self.class.host = host
18
+ self.class.port = port.to_i
19
+ self.class.base = base
20
+ self.class.groups = groups
21
+ self.class.logger = logger
22
+ end
23
+
24
+ def authenticate(username, password)
25
+ user_entry = nil
26
+
27
+ # Have to use the username DN format below for the bind operation to succeed.
28
+ auth = { method: :simple, username: "uid=#{username},ou=Users,#{self.class.base}", password: password }
29
+
30
+ Net::LDAP.open(host: self.class.host, port: self.class.port, base: self.class.base, auth: auth) do |ldap|
31
+ unless ldap.bind
32
+ result = ldap.get_operation_result
33
+ self.class.logger.error "LDAP authentication failed for '#{username}': #{result.message} (#{result.code})"
34
+ return nil
35
+ end
36
+
37
+ self.class.logger.info "LDAP authentication succeeded for '#{username}'"
38
+ user_entry = entry_for(username, ldap) || nil
39
+
40
+ # The user must be a member of at least the "<zone>-users" group for authentication to be considered successful.
41
+ users_group = self.class.groups['users']
42
+ unless group_member?(users_group, username, ldap)
43
+ self.class.logger.error "LDAP authentication failed: '#{username}' is not a member of the '#{users_group}' group"
44
+ return nil
45
+ end
46
+
47
+ user_entry.groups = groups_for(username, ldap)
48
+ end
49
+
50
+ user_entry
51
+ end
52
+
53
+ private
54
+
55
+ # Returns the LDAP directory entry for a user.
56
+ def entry_for(username, ldap)
57
+ filter = Net::LDAP::Filter.construct("uid=#{username}")
58
+ attributes = %w(uid displayName employeeNumber) # employeeNumber is used to store the 2FA token.
59
+ user_entry = nil
60
+
61
+ succeeded = ldap.search(filter: filter, attributes: attributes, return_result: false) do |entry|
62
+ user_entry = UserEntry.new(entry.uid.first, entry.displayName.first, entry.employeeNumber.first)
63
+ end
64
+
65
+ unless succeeded
66
+ result = ldap.get_operation_result
67
+ self.class.logger.error "Error searching the LDAP directory using filter '#{filter}': #{result.message} (#{result.code})"
68
+ end
69
+
70
+ user_entry
71
+ end
72
+
73
+ # Returns the LDAP groups a user belongs to.
74
+ def groups_for(username, ldap)
75
+ filter = Net::LDAP::Filter.construct("(&(objectClass=posixGroup)(memberUid=#{username}))")
76
+ attributes = %w(cn)
77
+ groups = []
78
+ succeeded = ldap.search(filter: filter, attributes: attributes, return_result: false) do |entry|
79
+ groups << entry[:cn].first
80
+ end
81
+ unless succeeded
82
+ result = ldap.get_operation_result
83
+ self.class.logger.error "Error searching the LDAP directory using filter '#{filter}': #{result.message} (#{result.code})"
84
+ end
85
+ groups
86
+ end
87
+
88
+ # Returns whether a user is a member of a group.
89
+ def group_member?(group, username, ldap)
90
+ self.class.logger.info "group: '#{group}'"
91
+ filter = Net::LDAP::Filter.construct("(&(cn=#{group})(memberUid=#{username}))")
92
+ attributes = %w(cn)
93
+ group_cn = nil
94
+
95
+ succeeded = ldap.search(filter: filter, attributes: attributes, return_result: false) do |entry|
96
+ group_cn = entry.cn.first
97
+ end
98
+
99
+ unless succeeded
100
+ result = ldap.get_operation_result
101
+ self.class.logger.error "Error searching the LDAP directory using filter '#{filter}': #{result.message} (#{result.code})"
102
+ end
103
+
104
+ group_cn == group
105
+ end
106
+ end
@@ -0,0 +1,8 @@
1
+ module ONSLDAP
2
+ module Version
3
+ MAJOR = 1
4
+ MINOR = 0
5
+ TINY = 0
6
+ end
7
+ VERSION = [Version::MAJOR, Version::MINOR, Version::TINY].compact * '.'
8
+ end
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ons-ldap
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - John Topley
8
+ - Philip Sharland
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2016-08-25 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: net-ldap
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: '0.11'
24
+ type: :runtime
25
+ prerelease: false
26
+ version_requirements: !ruby/object:Gem::Requirement
27
+ requirements:
28
+ - - "~>"
29
+ - !ruby/object:Gem::Version
30
+ version: '0'
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0.11'
34
+ description: Simple class for authenticating against an LDAP directory.
35
+ email:
36
+ - john.topley@ons.gov.uk
37
+ - philip.sharland@ons.gov.uk
38
+ executables: []
39
+ extensions: []
40
+ extra_rdoc_files: []
41
+ files:
42
+ - README.md
43
+ - lib/ons-ldap.rb
44
+ - lib/ons-ldap/ldap_connection.rb
45
+ - lib/ons-ldap/version.rb
46
+ homepage: https://github.com/ONSdigital/ldap-rubygem
47
+ licenses:
48
+ - Crown Copyright (Office for National Statistics)
49
+ metadata: {}
50
+ post_install_message:
51
+ rdoc_options: []
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ requirements: []
65
+ rubyforge_project:
66
+ rubygems_version: 2.6.6
67
+ signing_key:
68
+ specification_version: 4
69
+ summary: LDAP connection class
70
+ test_files: []