visor-auth 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+