visor-auth 0.0.1

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.
data/bin/visor-admin ADDED
@@ -0,0 +1,282 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'uri'
5
+ require 'visor-common'
6
+ $:.unshift File.expand_path('../../lib', __FILE__)
7
+ require 'auth/version'
8
+ require 'auth/client'
9
+
10
+ # VISoR administration command line interface script.
11
+ #
12
+ # Commands:
13
+ #
14
+ #list Show all registered users
15
+ #get Show a specific user
16
+ #add Register a new user
17
+ #update Update an user
18
+ #delete Delete an user
19
+ #clean Delete all users
20
+ #help <cmd> Show help message for one of the above commands
21
+ #
22
+ # Run <visor -h> to get more usage help.
23
+ #
24
+ class VisorAdminCLI
25
+ include Visor::Common::Exception
26
+ include Visor::Common::Config
27
+
28
+ # VISoR Admin CLI version
29
+ VERSION = '0.0.1'
30
+
31
+ attr_reader :argv, :options, :parser, :command
32
+
33
+ # Initialize a new CLI
34
+ def initialize(argv=ARGV)
35
+ @argv = argv
36
+ @options = load_conf_file
37
+ @parser = parser
38
+ @command = parse!
39
+ end
40
+
41
+ # OptionParser parser
42
+ def parser
43
+ OptionParser.new do |opts|
44
+ opts.banner = "Usage: visor-admin <command> [options]"
45
+
46
+ opts.separator ""
47
+ opts.separator "Commands:"
48
+ opts.separator " list Show all registered users"
49
+ opts.separator " get Show a specific user"
50
+ opts.separator " add Register a new user"
51
+ opts.separator " update Update an user"
52
+ opts.separator " delete Delete an user"
53
+ opts.separator " clean Delete all users"
54
+ opts.separator " help <cmd> Show help message for one of the above commands"
55
+
56
+ opts.separator ""
57
+ opts.separator "Options:"
58
+ opts.on("-a", "--access KEY", "The user access key (username)") { |key| options[:access_key] = key }
59
+ opts.on("-e", "--email ADDRESS", "The user email address") { |addr| options[:email] = addr }
60
+ opts.on("-q", "--query QUERY", "HTTP query like string to filter results") do |query|
61
+ (options[:query] = URI.decode_www_form(query)) rescue abort "The provided query string is not valid."
62
+ end
63
+
64
+ opts.separator ""
65
+ opts.separator "Common options:"
66
+ opts.on_tail("-D", "--dry-run", "Don't persist results, just print what would it do") { options[:dry] = true }
67
+ opts.on_tail('-v', '--verbose', "Enable verbose") { options[:verbose] = true }
68
+ opts.on_tail("-h", "--help", "Show this help message") { puts opts; exit 0 }
69
+ opts.on_tail('-V', '--version', "Show version") { puts "VISoR Admin CLI v#{VERSION}"; exit 0 }
70
+ end
71
+ end
72
+
73
+ # Parse argv arguments
74
+ def parse!
75
+ parser.parse! ARGV
76
+ ARGV.shift
77
+ end
78
+
79
+ # Parse the current shell arguments and run the command
80
+ def run!
81
+ abort parser.to_s if command.nil?
82
+ start = Time.now
83
+ case command
84
+ when 'list' then
85
+ list
86
+ when 'get' then
87
+ get
88
+ when 'add' then
89
+ add
90
+ when 'update' then
91
+ update
92
+ when 'delete' then
93
+ delete
94
+ when 'help' then
95
+ help
96
+ else
97
+ abort "Unknown command '#{command}'"
98
+ end
99
+ finish = Time.now
100
+ printf("Done in %-0.4f seconds", finish - start) if verbose?
101
+ exit 0
102
+ end
103
+
104
+ # Show all registered users
105
+ def list
106
+ users = client.get_users(options[:query])
107
+ puts "Found #{users.size} users..."
108
+ print_users(users)
109
+ rescue NotFound => e
110
+ puts e.message
111
+ rescue => e
112
+ abort "Failure while executing 'list':\n#{e.message}"
113
+ end
114
+
115
+ # Retrieve an user
116
+ def get
117
+ access_key = argv.shift
118
+ abort "No user access key provided as first argument, please provide it." unless access_key
119
+ user = client.get_user(access_key)
120
+ print_users(user)
121
+ rescue NotFound => e
122
+ puts e.message
123
+ rescue => e
124
+ abort "Failure while executing 'get':\n#{e.message}"
125
+ end
126
+
127
+ # Add a new user
128
+ def add
129
+ begin
130
+ info = parse_info_from_args
131
+ user = client.post_user(info)
132
+ puts "Successfully added new user with access key '#{user[:access_key]}'."
133
+ print_users(user)
134
+ rescue NotFound => e
135
+ puts e.message
136
+ rescue => e
137
+ abort "Failure while executing 'add':\n#{e.message}"
138
+ end
139
+ end
140
+
141
+ # Update an user
142
+ def update
143
+ access_key = argv.shift
144
+ abort "No user access key provided as first argument, please provide it." unless access_key
145
+ begin
146
+ info = parse_info_from_args
147
+ user = client.put_user(access_key, info)
148
+ puts "Successfully updated the user #{access_key}."
149
+ print_users(user) if verbose?
150
+ rescue NotFound => e
151
+ puts e.message
152
+ rescue => e
153
+ abort "Failure while executing 'update':\n#{e.message}"
154
+ end
155
+ end
156
+
157
+ # Delete an user
158
+ def delete
159
+ begin
160
+ argv.each do |access_key|
161
+ abort "No user access key provided as first argument, please provide it." unless access_key
162
+ user = client.delete_user(access_key)
163
+ puts "Successfully deleted user #{access_key}."
164
+ print_users(user) if verbose?
165
+ end
166
+ end
167
+ rescue NotFound => e
168
+ puts e.message
169
+ rescue => e
170
+ abort "Failure while executing 'delete':\n#{e.message}"
171
+ end
172
+
173
+ # Show help message for one of the above commands
174
+ def help
175
+ cmd = argv[0]
176
+ abort "Please provide a command name as argument (example: visor-admin help list)." unless cmd
177
+
178
+ case cmd.to_sym
179
+ when :list
180
+ puts %q[Usage: visor-admin list [options]
181
+
182
+ Returns a list of all registered users.
183
+
184
+ You can filter results based on a query using the --query (-q) option.
185
+
186
+ Examples:
187
+ $ visor-admin list
188
+ $ visor-admin list --query email=foo@bar.com]
189
+ when :get
190
+ puts %q[Usage: visor-admin get <ACCESS KEY> [options]
191
+
192
+ Returns the account information of the user with the given access key.
193
+
194
+ Examples:
195
+ $ visor get foo]
196
+ when :add
197
+ puts %q[Usage: visor-admin add <ATTRIBUTES> [options]
198
+
199
+ Add a new user, providing its attributes.
200
+
201
+ The following attributes can be specified as key/value pairs:
202
+
203
+ access_key: The wanted user access key (username)
204
+ email: The user email address
205
+
206
+ Examples:
207
+ $ visor-admin add access_key=foo email=foo@bar.com]
208
+ when :update
209
+ puts %q[Usage: visor-admin update <ACCESS KEY> [options]
210
+
211
+ Updates the account information of the user with the given access key.
212
+
213
+ The following attributes can be specified as key/value pairs:
214
+
215
+ access_key: The wanted user access key (username)
216
+ email: The user email address
217
+
218
+ Examples:
219
+ $ visor update foo email=bar@foo.com]
220
+ when :delete
221
+ puts %q[Usage: visor-admin delete <ACCESS KEY> [options]
222
+
223
+ Deletes the account of the user with the given access key.
224
+
225
+ Examples:
226
+ $ visor delete foo]
227
+ else
228
+ abort "Unknown command '#{cmd}'"
229
+ end
230
+ end
231
+
232
+ private
233
+
234
+ # Pretty print users
235
+ def print_users(user)
236
+ str = "%-37s %-18s %-41s %-27s %-24s %-24s\n"
237
+ printf(str, 'ID', 'ACCESS KEY', 'SECRET KEY', 'EMAIL', 'CREATED AT', 'UPDATED AT')
238
+ puts "#{'-'*36+" "+'-'*17+" "+'-'*40+" "+'-'*26+" "+'-'*23+" "+'-'*23}"
239
+
240
+ if user.is_a?(Array)
241
+ user.each { |u| printf(str, u[:_id], u[:access_key], u[:secret_key], u[:email] || '-', u[:created_at] || '-', u[:updated_at] || '-') }
242
+ else
243
+ printf(str, user[:_id], user[:access_key], user[:secret_key], user[:email] || '-', user[:created_at] || '-', user[:updated_at] || '-')
244
+ end
245
+ end
246
+
247
+ # Load configuration file options
248
+ def load_conf_file
249
+ config = Visor::Common::Config.load_config(:visor_auth)
250
+ {host: config[:bind_host], port: config[:bind_port]}
251
+ rescue => e
252
+ raise "There was an error loading the configuration file: #{e.message}"
253
+ end
254
+
255
+ # Get a new VISoR Auth Client instance
256
+ def client
257
+ Visor::Auth::Client.new(options)
258
+ end
259
+
260
+ # Find if verbose mode is active
261
+ def verbose?
262
+ options[:verbose]
263
+ end
264
+
265
+ # Parse key/value pair arguments to a valid hash
266
+ def parse_info_from_args
267
+ info = {}
268
+ raise "You should provide at least one key=value pair." if argv.empty?
269
+ argv.each do |arg|
270
+ k, v = arg.split('=')
271
+ raise "Arguments should be in the form of key=value pairs." unless k && v
272
+ info[k.downcase.sub('-', '_')] = v
273
+ end
274
+ info
275
+ end
276
+ end
277
+
278
+ # Execute if file is called
279
+ #if __FILE__ == $0
280
+ VisorAdminCLI.new.run!
281
+ #end
282
+
data/bin/visor-auth ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # VISoR Meta command line interface script.
4
+ # Run <visor-meta -h> to get more usage help.
5
+
6
+ require File.expand_path('../../lib/visor-auth', __FILE__)
7
+
8
+ Visor::Auth::CLI.new(Visor::Auth::Server, 'visor-auth').run!
@@ -0,0 +1,167 @@
1
+ require 'securerandom'
2
+
3
+ module Visor
4
+ module Auth
5
+ module Backends
6
+
7
+ # This is the Base super class for all Backends. Each new backend inherits from Base,
8
+ # which contains the model and all validations for the users metadata.
9
+ #
10
+ # Implementing a new backend is as simple as create a new backend class which inherits
11
+ # from Base and them implement the specific methods for querying the underlying database.
12
+ #
13
+ class Base
14
+
15
+ # Keys validation
16
+ #
17
+ # Mandatory attributes
18
+ MANDATORY = [:access_key, :email]
19
+ # Read-only attributes
20
+ READONLY = [:_id, :secret_key, :created_at, :updated_at]
21
+ # All attributes
22
+ ALL = MANDATORY + READONLY
23
+
24
+ attr_reader :db, :host, :port, :user, :password, :conn
25
+
26
+ # Initializes a Backend instance.
27
+ #
28
+ # @option [Hash] opts Any of the available options can be passed.
29
+ #
30
+ # @option opts [String] :host The host address.
31
+ # @option opts [Integer] :port The port to be used.
32
+ # @option opts [String] :db The wanted database.
33
+ # @option opts [String] :user The username to be authenticate db access.
34
+ # @option opts [String] :password The password to be authenticate db access.
35
+ # @option opts [Object] :conn The connection pool to access database.
36
+ #
37
+ def initialize(opts)
38
+ @host = opts[:host]
39
+ @port = opts[:port]
40
+ @db = opts[:db]
41
+ @user = opts[:user]
42
+ @password = opts[:password]
43
+ @conn = opts[:conn]
44
+ end
45
+
46
+ # Validates the user information for a post operation, based on possible keys and values.
47
+ #
48
+ # @param [Hash] info The user information.
49
+ #
50
+ # @raise[ArgumentError] If some of the information fields do not respect the
51
+ # possible values, contains any read-only or misses any mandatory field.
52
+ #
53
+ def validate_data_post(info)
54
+ info.assert_exclusion_keys(READONLY)
55
+ info.assert_inclusion_keys(MANDATORY)
56
+ validate_email(info[:email])
57
+ end
58
+
59
+ # Validates the user information for a put operation, based on possible keys and values.
60
+ #
61
+ # @param [Hash] info The user information.
62
+ #
63
+ # @raise[ArgumentError] If some of the metadata fields do not respect the
64
+ # possible values, contains any read-only or misses any mandatory field.
65
+ #
66
+ def validate_data_put(info)
67
+ info.assert_exclusion_keys(READONLY)
68
+ validate_email(info[:email]) if info[:email]
69
+ end
70
+
71
+ # Set protected fields value from a post operation.
72
+ # Being them the _id and created_at.
73
+ #
74
+ # @param [Hash] info The user information.
75
+ #
76
+ # @option [Hash] opts Any of the available options can be passed.
77
+ #
78
+ # @return [Hash] The updated user information.
79
+ #
80
+ def set_protected_post(info)
81
+ info.merge!(_id: SecureRandom.uuid, secret_key: SecureRandom.base64(30), created_at: Time.now)
82
+ end
83
+
84
+ # Set protected field value from a get operation.
85
+ # Being it the updated_at.
86
+ #
87
+ # @param [Hash] info The user information update.
88
+ #
89
+ # @return [Hash] The updated user information.
90
+ #
91
+ def set_protected_put(info)
92
+ info.merge!(updated_at: Time.now)
93
+ end
94
+
95
+ # Generates a compatible SQL WHERE string from a hash.
96
+ #
97
+ # @param [Hash] h The input hash.
98
+ #
99
+ # @return [String] A string as "k='v' AND k1='v1'",
100
+ # only Strings Times or Hashes values are surrounded with '<value>'.
101
+ #
102
+ def to_sql_where(h)
103
+ h.map { |k, v| string_time_or_hash?(v) ? "#{k}='#{v}'" : "#{k}=#{v}" }.join(' AND ')
104
+ end
105
+
106
+ # Generates a compatible SQL UPDATE string from a hash.
107
+ #
108
+ # @param [Hash] h The input hash.
109
+ #
110
+ # @return [String] A string as "k='v', k1='v1'",
111
+ # only Strings Times or Hashes values are surrounded with '<value>'.
112
+ #
113
+ def to_sql_update(h)
114
+ h.map { |k, v| string_time_or_hash?(v) ? "#{k}='#{v}'" : "#{k}=#{v}" }.join(', ')
115
+ end
116
+
117
+ # Generates a compatible SQL INSERT string from a hash.
118
+ #
119
+ # @param [Hash] h The input hash.
120
+ #
121
+ # @return [String] A string as "(k, k1) VALUES ('v', 'v1')",
122
+ # only Strings Times or Hashes values are surrounded with '<value>'.
123
+ #
124
+ def to_sql_insert(h)
125
+ surround = h.values.map { |v| string_time_or_hash?(v) ? "'#{v}'" : v }
126
+ %W{(#{h.keys.join(', ')}) (#{surround.join(', ')})}
127
+ end
128
+
129
+ # Validates that incoming query filters fields are valid.
130
+ #
131
+ # @param [Hash] filters The image metadata filters coming from a GET request.
132
+ #
133
+ # @raise[ArgumentError] If some of the query filter fields do not respect the
134
+ # possible values.
135
+ #
136
+ def validate_query_filters(filters)
137
+ filters.symbolize_keys!
138
+ filters.assert_valid_keys(ALL)
139
+ end
140
+
141
+ private
142
+
143
+ # Verifies if a given object is a String, a Time or a Hash.
144
+ #
145
+ # @param [Object] v The input value.
146
+ #
147
+ # @return [true, false] If the provided value is or not a String, a Time or a Hash.
148
+ #
149
+ def string_time_or_hash?(v)
150
+ v.is_a?(String) or v.is_a?(Time) or v.is_a?(Hash)
151
+ end
152
+
153
+ # Verifies if a given email string is a valid email.
154
+ #
155
+ # @param [String] s The input value.
156
+ #
157
+ # @raise [ArgumentError] If the email address is invalid.
158
+ #
159
+ def validate_email(s)
160
+ valid = s.match(/([^@\s*]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})/i)
161
+ raise ArgumentError, "The email address seems to be invalid." unless valid
162
+ end
163
+
164
+ end
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,165 @@
1
+ require 'mongo'
2
+ require 'uri'
3
+
4
+ module Visor
5
+ module Auth
6
+ module Backends
7
+
8
+ # The MongoDB Backend for the VISoR Auth.
9
+ #
10
+ class MongoDB < Base
11
+
12
+ include Visor::Common::Exception
13
+
14
+ # Connection constants
15
+ #
16
+ # Default MongoDB database
17
+ DEFAULT_DB = 'visor'
18
+ # Default MongoDB host address
19
+ DEFAULT_HOST = '127.0.0.1'
20
+ # Default MongoDB host port
21
+ DEFAULT_PORT = 27017
22
+ # Default MongoDB user
23
+ DEFAULT_USER = nil
24
+ # Default MongoDB password
25
+ DEFAULT_PASSWORD = nil
26
+
27
+ # Initializes a MongoDB Backend instance.
28
+ #
29
+ # @option [Hash] opts Any of the available options can be passed.
30
+ #
31
+ # @option opts [String] :uri The connection uri, if provided, no other option needs to be setted.
32
+ # @option opts [String] :db (DEFAULT_DB) The wanted database.
33
+ # @option opts [String] :host (DEFAULT_HOST) The host address.
34
+ # @option opts [Integer] :port (DEFAULT_PORT) The port to be used.
35
+ # @option opts [String] :user (DEFAULT_USER) The user to be used.
36
+ # @option opts [String] :password (DEFAULT_PASSWORD) The password to be used.
37
+ # @option opts [Object] :conn The connection pool to access database.
38
+ #
39
+ def self.connect(opts = {})
40
+ opts[:uri] ||= ''
41
+ uri = URI.parse(opts[:uri])
42
+ opts[:db] = uri.path ? uri.path.gsub('/', '') : DEFAULT_DB
43
+ opts[:host] = uri.host || DEFAULT_HOST
44
+ opts[:port] = uri.port || DEFAULT_PORT
45
+ opts[:user] = uri.user || DEFAULT_USER
46
+ opts[:password] = uri.password || DEFAULT_PASSWORD
47
+
48
+ self.new opts
49
+ end
50
+
51
+ def initialize(opts)
52
+ super opts
53
+ @conn = connection
54
+ end
55
+
56
+ # Establishes and returns a MongoDB database connection.
57
+ #
58
+ # @return [Mongo::Collection] A MongoDB collection object.
59
+ #
60
+ def connection
61
+ db = Mongo::Connection.new(@host, @port, :pool_size => 10, :pool_timeout => 5).db(@db)
62
+ db.authenticate(@user, @password) unless @user.empty? && @password.empty?
63
+ db.collection('users')
64
+ end
65
+
66
+ # Returns an array with the registered users.
67
+ #
68
+ # @option [Hash] filters Users attributes for filtering the returned results.
69
+ # Besides common attributes filters, the following options can be passed to.
70
+ #
71
+ # @return [Array] The users information.
72
+ #
73
+ # @raise [NotFound] If there are no registered users.
74
+ #
75
+ def get_users(filters = {})
76
+ validate_query_filters filters unless filters.empty?
77
+ users = @conn.find(filters).to_a
78
+ raise NotFound, "No users found." if users.empty? && filters.empty?
79
+ raise NotFound, "No users found with given parameters." if users.empty?
80
+ users
81
+ end
82
+
83
+ # Returns an user information.
84
+ #
85
+ # @param [String] access_key The user access_key.
86
+ #
87
+ # @return [BSON::OrderedHash] The requested user information.
88
+ #
89
+ # @raise [NotFound] If user was not found.
90
+ #
91
+ def get_user(access_key)
92
+ user = @conn.find_one(access_key: access_key)
93
+ raise NotFound, "No user found with Access Key '#{access_key}'." unless user
94
+ user
95
+ end
96
+
97
+ # Delete a registered user.
98
+ #
99
+ # @param [String] access_key The user access_key.
100
+ #
101
+ # @return [BSON::OrderedHash] The deleted image metadata.
102
+ #
103
+ # @raise [NotFound] If user was not found.
104
+ #
105
+ def delete_user(access_key)
106
+ user = @conn.find_one(access_key: access_key)
107
+ raise NotFound, "No user found with Access Key '#{access_key}'." unless user
108
+ @conn.remove(access_key: access_key)
109
+ user
110
+ end
111
+
112
+ # Delete all registered users.
113
+ #
114
+ def delete_all!
115
+ @conn.remove
116
+ end
117
+
118
+ # Create a new user record for the given information.
119
+ #
120
+ # @param [Hash] user The user information.
121
+ #
122
+ # @return [BSON::OrderedHash] The already added user information.
123
+ #
124
+ # @raise [Invalid] If user information validation fails.
125
+ # @raise [ConflictError] If an access_key was already taken.
126
+ #
127
+ def post_user(user)
128
+ validate_data_post user
129
+ exists = @conn.find_one(access_key: user[:access_key])
130
+ raise ConflictError, "The Access Key '#{user[:access_key]}' was already taken." if exists
131
+ set_protected_post user
132
+ @conn.insert(user)
133
+ self.get_user(user[:access_key])
134
+ end
135
+
136
+ # Update an user information.
137
+ #
138
+ # @param [String] access_key The user access_key.
139
+ # @param [Hash] update The user information update.
140
+ #
141
+ # @return [BSON::OrderedHash] The updated user information.
142
+ #
143
+ # @raise [Invalid] If user information validation fails.
144
+ # @raise [ConflictError] If an access_key was already taken.
145
+ # @raise [NotFound] If user was not found.
146
+ #
147
+ def put_user(access_key, update)
148
+ validate_data_put update
149
+ user = @conn.find_one(access_key: access_key)
150
+ raise NotFound, "No user found with Access Key '#{access_key}'." unless user
151
+ if update[:access_key]
152
+ exists = @conn.find_one(access_key: update[:access_key])
153
+ raise ConflictError, "The Access Key '#{update[:access_key]}' was already taken." if exists
154
+ end
155
+ set_protected_put update
156
+ @conn.update({access_key: access_key}, :$set => update)
157
+ self.get_user(update[:access_key] || access_key)
158
+ end
159
+
160
+ end
161
+ end
162
+ end
163
+ end
164
+
165
+