open_directory_utils 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,127 @@
1
+ require 'net/ssh'
2
+ require "open_directory_utils/dscl"
3
+ require "open_directory_utils/pwpolicy"
4
+ require "open_directory_utils/commands_user"
5
+ require "open_directory_utils/commands_group"
6
+
7
+ module OpenDirectoryUtils
8
+ class Connection
9
+
10
+ attr_reader :srv_info, :dir_info
11
+
12
+ include OpenDirectoryUtils::Dscl
13
+ include OpenDirectoryUtils::Pwpolicy
14
+ include OpenDirectoryUtils::CommandsUser
15
+ include OpenDirectoryUtils::CommandsGroup
16
+
17
+ # configure connection with ENV_VARS (or parameters)
18
+ # @params [Hash] - reqiured info includes: srv_hostname:, srv_username: (password: if not using ssh-keys)
19
+ # @note - mostly likely needed for better security is -- dir_username:, dir_password:
20
+ def initialize(params={})
21
+ config = defaults.merge(params)
22
+
23
+ @srv_info = { hostname: config[:srv_hostname],
24
+ username: config[:srv_username],
25
+ ssh_options: config[:ssh_options]}
26
+ @dir_info = { username: config[:dir_username],
27
+ password: config[:dir_password],
28
+ data_path: config[:dir_datapath],
29
+ dscl: config[:dscl_path],
30
+ pwpol: config[:pwpol_path],
31
+ }
32
+ raise ArgumentError, 'server hostname missing' if srv_info[:hostname].nil? or
33
+ srv_info[:hostname].empty?
34
+ raise ArgumentError, 'server username missing' if srv_info[:username].nil? or
35
+ srv_info[:username].empty?
36
+ end
37
+
38
+ # after configuring a connection with .new - send commands via ssh to open directory
39
+ # @command [Symbol] - required -- to choose the action wanted
40
+ # @params [Hash] - required -- necessary information to accomplish action
41
+ # @output [String] - optional -- 'xml' or 'plist' will return responses using xml format
42
+ def run(command:, params:, output: nil)
43
+ answer = {}
44
+ params[:format] = output
45
+ # just in case clear record_name and calculate later
46
+ params[:record_name] = nil
47
+ ssh_cmds = send(command, params, dir_info)
48
+ results = send_cmds_to_od_server(ssh_cmds)
49
+ # pp ssh_cmds
50
+ # pp results
51
+ format_results(results, command, params, ssh_cmds)
52
+ rescue ArgumentError, NoMethodError => error
53
+ {error: {response: error.message, command: command,
54
+ attributes: params, dscl_cmds: ssh_cmds}}
55
+ end
56
+
57
+ private
58
+
59
+ def send_cmds_to_od_server(cmds)
60
+ cmd_array = Array( cmds )
61
+ output = []
62
+ Net::SSH.start( srv_info[:hostname], srv_info[:username],
63
+ srv_info[:ssh_options] ) do |ssh|
64
+ cmd_array.each do |one_cmd|
65
+ output << (ssh.exec!(one_cmd)).strip
66
+ end
67
+ end
68
+ return output
69
+ end
70
+
71
+ def format_results(results, command, params, ssh_cmds)
72
+ errors = true if results.to_s.include? 'Error'
73
+ errors = false unless results.to_s.include? 'Error'
74
+
75
+ if command.eql?(:user_exists?) or command.eql?(:group_exists?)
76
+ errors = false # in this case not actually an error
77
+ unless results.to_s.include?('eDSRecordNotFound')
78
+ results = [true]
79
+ else
80
+ results = [false]
81
+ end
82
+ end
83
+
84
+ if command.eql?(:user_in_group?) or command.eql?(:group_has_user?)
85
+ username = nil
86
+ username = username || params[:user_name]
87
+ username = username || params[:username]
88
+ username = username || params[:uid]
89
+ username = username.to_s.strip
90
+
91
+ raise ArgumentError, "username invalid or missing" if username.eql? '' or username.include? ' '
92
+ raise ArgumentError, "groupname invalid or missing" if results.to_s.include?('eDSRecordNotFound')
93
+
94
+ if results.to_s.include?( username )
95
+ results = [true]
96
+ else
97
+ results = [false]
98
+ end
99
+ end
100
+
101
+ ans = case errors
102
+ when false
103
+ {success:{response: results, command: command, attributes: params}}
104
+ else
105
+ {error: {response: results, command: command,
106
+ attributes: params, dscl_cmds: ssh_cmds}}
107
+ end
108
+ return ans
109
+ end
110
+
111
+ def defaults
112
+ {
113
+ srv_hostname: ENV['OD_HOSTNAME'],
114
+ srv_username: ENV['OD_USERNAME'],
115
+ ssh_options: (eval(ENV['OD_SSH_OPTIONS'].to_s) || {}),
116
+
117
+ dir_username: ENV['DIR_ADMIN_USER'],
118
+ dir_password: ENV['DIR_ADMIN_PASS'],
119
+ dir_datapath: (ENV['DIR_DATAPATH'] || '/LDAPv3/127.0.0.1/'),
120
+
121
+ dscl_path: ENV['DSCL_PATH'] || '/usr/bin/dscl',
122
+ pwpol_path: ENV['PWPOL_PATH'] || '/usr/bin/pwpolicy'
123
+ }
124
+ end
125
+
126
+ end
127
+ end
@@ -0,0 +1,53 @@
1
+ require "open_directory_utils/clean_check"
2
+
3
+ module OpenDirectoryUtils
4
+
5
+ # https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man1/dscl.1.html
6
+ # https://superuser.com/questions/592921/mac-osx-users-vs-dscl-command-to-list-user/621055?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa
7
+ module Dscl
8
+
9
+ include OpenDirectoryUtils::CleanCheck
10
+
11
+ # builds the dscl command (with complete flexibility)
12
+ # attribs [Hash] - required - :record_name (the resource to affect), :action (create, append, delete, passwd, etc), attribute: (resource attribute to change), value: (value to add to attribute)
13
+ # dir_info [Hash] - usually configured in the connection initializer and then passed to dscl to build command correctly
14
+ def dscl(attribs, dir_info)
15
+ check_critical_attribute( attribs, :record_name )
16
+ check_critical_attribute( attribs, :action )
17
+ check_critical_attribute( attribs, :scope )
18
+ tidy_attribs = tidy_attribs(attribs)
19
+ build_dscl_command( tidy_attribs, dir_info )
20
+ end
21
+
22
+ # TODO: switch to template pattern
23
+ def build_dscl_command(attribs, dir_info)
24
+ # allow :recordname to be passed-in if using dscl directly
25
+ attribs[:record_name] = attribs[:record_name] || attribs[:recordname]
26
+ # /usr/bin/dscl -u diradmin -P "BigSecret" /LDAPv3/127.0.0.1/ -append /Users/$UID_USERNAME apple-keyword "$VALUE"
27
+ # "/usr/bin/dscl -plist -u #{od_username} -P #{od_password} #{od_dsclpath} -#{command} #{resource} #{params}"
28
+ ans = "#{dir_info[:dscl]}"
29
+ unless attribs[:format].nil?
30
+ ans += ' -plist' if attribs[:format].eql? 'plist' or
31
+ attribs[:format].eql? 'xml'
32
+ end
33
+ ans += " -u #{dir_info[:username]}" unless dir_info[:username].nil? or
34
+ dir_info[:username].empty? or
35
+ attribs[:action].eql? 'auth'
36
+ ans += %Q[ -P "#{dir_info[:password]}"] unless dir_info[:password].nil? or
37
+ dir_info[:password].empty? or
38
+ attribs[:action].eql? 'auth'
39
+ ans += " #{dir_info[:data_path]}"
40
+
41
+ ans += %Q[ -#{attribs[:action]}]
42
+ ans += %Q[ #{attribs[:record_name]}] if attribs[:action].eql? 'auth'
43
+ ans += %Q[ /#{attribs[:scope]}/#{attribs[:record_name]}] unless
44
+ attribs[:action].eql? 'auth'
45
+ ans += %Q[ #{attribs[:attribute]}] unless attribs[:attribute].nil? or
46
+ attribs[:attribute].empty?
47
+ ans += %Q[ "#{attribs[:value]}"] unless attribs[:value].nil? or
48
+ attribs[:value].empty?
49
+ return ans
50
+ end
51
+
52
+ end
53
+ end
@@ -0,0 +1,48 @@
1
+ require "open_directory_utils/clean_check"
2
+
3
+ module OpenDirectoryUtils
4
+
5
+ # https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man1/dscl.1.html
6
+ # https://superuser.com/questions/592921/mac-osx-users-vs-dscl-command-to-list-user/621055?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa
7
+ module Pwpolicy
8
+
9
+ include OpenDirectoryUtils::CleanCheck
10
+
11
+ def build_pwpolicy_command(params, dir_info)
12
+ # /usr/bin/pwpolicy -a diradmin -p "BigSecret" -u username -setpolicy "isDisabled=0"
13
+ ans = "#{dir_info[:pwpol]}"
14
+ ans += " -a #{dir_info[:diradmin]}" unless dir_info[:diradmin].nil? or
15
+ dir_info[:diradmin].empty?
16
+ ans += %Q[ -p "#{dir_info[:password]}"] unless dir_info[:password].nil? or
17
+ dir_info[:password].empty?
18
+ ans += %Q[ -u #{params[:record_name]}]
19
+ ans += %Q[ -#{params[:attribute]}]
20
+ ans += %Q[ "#{params[:value]}"] unless params[:value].nil? or
21
+ params[:value].empty?
22
+ return ans
23
+ end
24
+
25
+ def pwpolicy(params, dir_info)
26
+ check_critical_attribute( params, :record_name )
27
+ cmd_params = tidy_attribs(params)
28
+
29
+ build_pwpolicy_command( cmd_params, dir_info )
30
+ end
31
+
32
+ ## PRE-BUILT commands
33
+ #####################
34
+ # /usr/bin/pwpolicy -a diradmin -p A-B1g-S3cret -u $shortname_USERNAME -setpolicy "isDisabled=0"
35
+ def user_enable_login(params, dir_info)
36
+ command = {attribute: 'enableuser'}
37
+ params = command.merge(params)
38
+ pwpolicy(params, dir_info)
39
+ end
40
+ # /usr/bin/pwpolicy -a diradmin -p A-B1g-S3cret -u $shortname_USERNAME -setpolicy "isDisabled=1"
41
+ def user_disable_login(params, dir_info)
42
+ command = {attribute: 'disableuser'}
43
+ params = command.merge(params)
44
+ pwpolicy(params, dir_info)
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,188 @@
1
+ module OpenDirectoryUtils
2
+ # command pattern
3
+ # https://makandracards.com/alexander-m/43748-command-pattern
4
+ # https://stackoverflow.com/questions/43535421/command-pattern-in-ruby?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa
5
+ #
6
+ # DSCL
7
+ # https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man1/dscl.1.html
8
+ # https://superuser.com/questions/592921/mac-osx-users-vs-dscl-command-to-list-user/621055?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa
9
+ class Commands
10
+ class Error < StandardError; end
11
+
12
+ def initialize(params)
13
+ end
14
+
15
+ def execute
16
+ raise NotYetImplemented
17
+ end
18
+ end
19
+
20
+ # # get all usernames -- dscl . -list /Users
21
+ # # get all user details -- dscl . -readall /Users
22
+ # def user_exists?
23
+ # end
24
+ class UserGetInfo
25
+ # get user record -- dscl . -read /Users/<username>
26
+ # get user value -- dscl . -read /Users/<username> <key>
27
+ # search od user -- dscl . -search /Users RealName "Andrew Garrett"
28
+ # return as xml -- dscl -plist . -search /Users RealName "Andrew Garrett"
29
+ def user_get_info
30
+ end
31
+ end
32
+
33
+ # https://images.apple.com/server/docs/Command_Line.pdf
34
+ # https://serverfault.com/questions/20702/how-do-i-create-user-accounts-from-the-terminal-in-mac-os-x-10-5?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa
35
+ # https://superuser.com/questions/1154564/how-to-create-a-user-from-the-macos-command-line
36
+ # There are a few steps to create a user account from the command line. The good news is that you're using the right tool, dscl. What you're missing are the separate components that comprise a user account. You have to create these manually.
37
+ # sudo dscl . -create /Users/someuser
38
+ # sudo dscl . -create /Users/someuser UserShell /bin/bash
39
+ # sudo dscl . -create /Users/someuser RealName "Lucius Q. User"
40
+ # sudo dscl . -create /Users/someuser UniqueID "1010" #use something not already in use
41
+ # sudo dscl . -create /Users/someuser PrimaryGroupID 80
42
+ # sudo dscl . -create /Users/someuser NFSHomeDirectory /Users/soemuser
43
+ #
44
+ # You can then use passwd to change the user's password, or use:
45
+ # sudo dscl . -passwd /Users/someuser password
46
+
47
+ # You'll also have to create the user's home directory and change ownership so the user can access it. And be sure that the UniqueID is, in fact, unique.
48
+ #
49
+ # This line will add the user to the administrator's group:
50
+ # sudo dscl . -append /Groups/admin GroupMembership someuser
51
+ def user_create
52
+ end
53
+
54
+ # add 1st user -- dscl . create /Groups/ladmins GroupMembership localadmin
55
+ # add more users -- dscl . append /Groups/ladmins GroupMembership 2ndlocaladmin
56
+ def user_add_to_group
57
+ end
58
+
59
+ # /usr/bin/dscl -u diradmin -P A-B1g-S3cret /LDAPv3/127.0.0.1/ -delete /Groups/$VALUE GroupMembership $UID_USERNAME
60
+ def user_remove_from_group
61
+ end
62
+
63
+ # dscl . -delete /Users/yourUserName
64
+ # https://tutorialforlinux.com/2011/09/15/delete-users-and-groups-from-terminal/
65
+ def user_delete
66
+ end
67
+
68
+ # /usr/bin/dscl -plist -u diradmin -P #{adminpw} /LDAPv3/127.0.0.1/ -passwd /Users/#{uid} #{passwd}
69
+ def user_set_password
70
+ end
71
+
72
+ # /usr/bin/dscl /LDAPv3/127.0.0.1 auth #{uid} #{passwd}
73
+ def user_test_password
74
+ end
75
+
76
+ # /usr/bin/pwpolicy -a diradmin -p A-B1g-S3cret -u $UID_USERNAME -setpolicy "isDisabled=0"
77
+ def user_enable_login
78
+ end
79
+
80
+ # /usr/bin/pwpolicy -a diradmin -p A-B1g-S3cret -u $UID_USERNAME -setpolicy "isDisabled=1"
81
+ def user_disable_login
82
+ end
83
+
84
+ # /usr/bin/dscl -u diradmin -P A-B1g-S3cret /LDAPv3/127.0.0.1/ -create /Users/$UID_USERNAME gidnumber "$VALUE"
85
+ def user_set_groupnumber
86
+ end
87
+
88
+ # /usr/bin/dscl -u diradmin -P A-B1g-S3cret /LDAPv3/127.0.0.1/ -create /Users/$UID_USERNAME givenName "$VALUE"
89
+ def user_set_first_name
90
+ end
91
+
92
+ # /usr/bin/dscl -u diradmin -P A-B1g-S3cret /LDAPv3/127.0.0.1/ -create /Users/$UID_USERNAME sn "$VALUE"
93
+ def user_set_last_name
94
+ end
95
+
96
+ # /usr/bin/dscl -u diradmin -P A-B1g-S3cret /LDAPv3/127.0.0.1/ -create /Users/$UID_USERNAME apple-namesuffix "$VALUE"
97
+ def user_set_name_suffix
98
+ end
99
+
100
+ # /usr/bin/dscl -u diradmin -P A-B1g-S3cret /LDAPv3/127.0.0.1/ -create /Users/$UID_USERNAME mail "$VALUE"
101
+ # /usr/bin/dscl -u diradmin -P A-B1g-S3cret /LDAPv3/127.0.0.1/ -create /Users/$UID_USERNAME email "$VALUE"
102
+ # /usr/bin/dscl -u diradmin -P A-B1g-S3cret /LDAPv3/127.0.0.1/ -create /Users/$UID_USERNAME apple-user-mailattribute "$VALUE"
103
+ def user_set_email
104
+ end
105
+
106
+ # create first keyword
107
+ # /usr/bin/dscl -u diradmin -P A-B1g-S3cret /LDAPv3/127.0.0.1/ -create /Users/$UID_USERNAME apple-keyword "$VALUE"
108
+ # add a keyword
109
+ # /usr/bin/dscl -u diradmin -P A-B1g-S3cret /LDAPv3/127.0.0.1/ -append /Users/$UID_USERNAME apple-keyword "$VALUE"
110
+ def user_set_keywords
111
+ end
112
+
113
+ # /usr/bin/dscl -u diradmin -P A-B1g-S3cret /LDAPv3/127.0.0.1/ -append /Users/$UID_USERNAME apple-keyword "$VALUE"
114
+ def user_add_keywords
115
+ end
116
+
117
+ # /usr/bin/dscl -u diradmin -P A-B1g-S3cret /LDAPv3/127.0.0.1/ -create /Users/$UID_USERNAME mobile "$VALUE"
118
+ def user_set_mobile_phone
119
+ end
120
+
121
+ # /usr/bin/dscl -u diradmin -P A-B1g-S3cret /LDAPv3/127.0.0.1/ -create /Users/$UID_USERNAME telephoneNumber "$VALUE"
122
+ def user_set_work_phone
123
+ end
124
+
125
+ # /usr/bin/dscl -u diradmin -P A-B1g-S3cret /LDAPv3/127.0.0.1/ -create /Users/$UID_USERNAME homePhone "$VALUE"
126
+ def user_set_home_phone
127
+ end
128
+
129
+ # /usr/bin/dscl -u diradmin -P A-B1g-S3cret /LDAPv3/127.0.0.1/ -create /Users/$UID_USERNAME title "$VALUE"
130
+ def user_set_title
131
+ end
132
+
133
+ # /usr/bin/dscl -u diradmin -P A-B1g-S3cret /LDAPv3/127.0.0.1/ -create /Users/$UID_USERNAME homedirectory "$VALUE"
134
+ def user_set_home_directoy
135
+ end
136
+
137
+ # /usr/bin/dscl -u diradmin -P A-B1g-S3cret /LDAPv3/127.0.0.1/ -create /Users/$UID_USERNAME loginShell "$VALUE"
138
+ def user_set_shell
139
+ end
140
+
141
+ # /usr/bin/dscl -u diradmin -P A-B1g-S3cret /LDAPv3/127.0.0.1/ -create /Users/$UID_USERNAME apple-company "$VALUE"
142
+ def user_set_company
143
+ end
144
+ alias_method :las_program_info, :user_set_company
145
+
146
+ # /usr/bin/dscl -u diradmin -P A-B1g-S3cret /LDAPv3/127.0.0.1/ -create /Users/$UID_USERNAME street "$VALUE"
147
+ def user_set_street
148
+ end
149
+ alias_method :las_, :user_set_street
150
+
151
+ # /usr/bin/dscl -u diradmin -P A-B1g-S3cret /LDAPv3/127.0.0.1/ -create /Users/$UID l "$VALUE"
152
+ def user_set_city
153
+ end
154
+ alias_method :las_, :user_set_city
155
+
156
+ # /usr/bin/dscl -u diradmin -P A-B1g-S3cret /LDAPv3/127.0.0.1/ -create /Users/$UID_USERNAME st "$VALUE"
157
+ def user_set_state
158
+ end
159
+ alias_method :las_cultural_trip, :user_set_state
160
+
161
+ # /usr/bin/dscl -u diradmin -P A-B1g-S3cret /LDAPv3/127.0.0.1/ -create /Users/$UID_USERNAME postalCode "$VALUE"
162
+ def user_set_postcode
163
+ end
164
+ alias_method :las_faculty_family, :user_set_postcode
165
+
166
+ # /usr/bin/dscl -u diradmin -P A-B1g-S3cret /LDAPv3/127.0.0.1/ -create /Users/$UID_USERNAME apple-webloguri "$VALUE"
167
+ def user_set_blog
168
+ end
169
+ alias_method :las_, :user_set_blog
170
+
171
+ # /usr/bin/dscl -u diradmin -P A-B1g-S3cret /LDAPv3/127.0.0.1/ -create /Users/$UID_USERNAME apple-organizationinfo "$VALUE"
172
+ def user_organizational_info
173
+ end
174
+ alias_method :las_link_student_to_parent, :user_organizational_info
175
+
176
+ # /usr/bin/dscl -u diradmin -P A-B1g-S3cret /LDAPv3/127.0.0.1/ -create /Users/$UID_USERNAME apple-relationships "$VALUE"
177
+ def user_relationships
178
+ end
179
+ alias_method :las_link_parent_to_student, :user_relationships
180
+
181
+ # /usr/bin/dscl -u diradmin -P A-B1g-S3cret /LDAPv3/127.0.0.1/ -create /Users/$UID_USERNAME labeledURI "$VALUE"
182
+ def user_set_homepage
183
+ end
184
+ alias_method :las_enrollment_date, :user_set_homepage
185
+ alias_method :las_start_date, :user_set_homepage
186
+
187
+ end
188
+ end
@@ -0,0 +1,5 @@
1
+ module OpenDirectoryUtils
2
+ module Version
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,38 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "open_directory_utils/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "open_directory_utils"
8
+ spec.version = OpenDirectoryUtils::Version::VERSION
9
+ spec.authors = ["Bill Tihen", "Lee Weisbecker"]
10
+ spec.email = ["btihen@gmail.com", "leeweisbecker@gmail.com"]
11
+
12
+ spec.summary = %q{A ruby wrapper to access MacOpenDirectory management commands remotely}
13
+ spec.description = %q{Create and update users and groups on a MacOpenDirectory Server}
14
+ spec.homepage = "https://github.com/btihen/open_directory_utils"
15
+ spec.license = "MIT"
16
+
17
+ # # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
18
+ # # to allow pushing to a single host or delete this section to allow pushing to any host.
19
+ # if spec.respond_to?(:metadata)
20
+ # spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
21
+ # else
22
+ # raise "RubyGems 2.0 or newer is required to protect against " \
23
+ # "public gem pushes."
24
+ # end
25
+
26
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
27
+ f.match(%r{^(test|spec|features)/})
28
+ end
29
+ spec.bindir = "exe"
30
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
31
+ spec.require_paths = ["lib"]
32
+
33
+ spec.add_dependency "net-ssh", "~> 4.2"
34
+
35
+ spec.add_development_dependency "bundler", "~> 1.16"
36
+ spec.add_development_dependency "rake", "~> 12.3"
37
+ spec.add_development_dependency "rspec", "~> 3.7"
38
+ end