sessionvoc-open 1.7.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,137 @@
1
+ # Copyright:: 2011 triAGENS GmbH
2
+ # Author:: Oliver Kiessler (mailto:kiessler@inceedo.com)
3
+ module Sessionvoc
4
+ module Open
5
+ module Authentification
6
+ # Performs the simple authentification method against the SessionVOC server.
7
+ # Currently not available.
8
+ # === Parameters
9
+ # * sid = Session Id
10
+ # * uid = User
11
+ # * password = Plaintext Password
12
+ # * options
13
+ def simple(sid, uid, password, options = {})
14
+ logger.debug("Authentification#simple for sid: #{sid} for uid: #{uid}")
15
+ password = Digest::MD5.hexdigest(password) if meta_data["authenticationMethod"] > 0 and meta_data["clientHash"] == 1
16
+ response = get_response(:put, "/session/#{sid}/authenticate", {:body => {'uid' => uid, 'password' => password}.to_json})
17
+ if response_ok?(response)
18
+ response.parsed_response
19
+ else
20
+ if options[:no_exception]
21
+ false
22
+ else
23
+ handle_exception(response.parsed_response["errorCode"], response.parsed_response["message"])
24
+ end
25
+ end
26
+ end
27
+
28
+ # Performs the challenge-response authentification method against the SessionVOC
29
+ # === Parameters
30
+ # * sid = Session Id
31
+ # * uid = User
32
+ # * password = User Password
33
+ # * options
34
+ def challenge(sid, uid, password, options = {})
35
+ logger.debug("Authentification#challenge for sid: #{sid} for uid: #{uid}")
36
+ response = get_response(:put, "/session/#{sid}/authenticate",
37
+ {:body => {'uid' => uid}.to_json})
38
+ if response_ok?(response) and response.parsed_response["loginData"]
39
+ return challenge_response(sid, uid, password, response.parsed_response["loginData"]['salt'],
40
+ response.parsed_response["loginData"]['hash'], options)
41
+ else
42
+ if options[:no_exception]
43
+ return nil
44
+ else
45
+ handle_exception(response.parsed_response["errorCode"], response.parsed_response["message"])
46
+ end
47
+ end
48
+ end
49
+
50
+ # Performs a user logout.
51
+ # === Parameters
52
+ # * sid = Session Id
53
+ def logout(sid, options = {})
54
+ logger.debug("Authentification#logout for sid: #{sid}")
55
+ response = get_response(:put, "/session/#{sid}/authenticate", {:body => nil})
56
+ response_ok?(response) ? true : handle_exception(response.parsed_response["errorCode"], response.parsed_response["message"])
57
+ end
58
+
59
+ # Create a (one time) usable nonce.
60
+ # === Parameters
61
+ # * ts = Timestamp (only used for testing)
62
+ # * random_number = (only used for testing)
63
+ # * options
64
+ def create_nonce(ts = nil, random_number = nil, options = {})
65
+ timestamp = ts ? ts.to_s : Time.now.to_i.to_s
66
+ if options[:no_encode]
67
+ "#{timestamp[0..3]}#{random_number ? random_number[4..11] : gen_64bit_id[4..11]}"
68
+ else
69
+ Base64.encode64("#{timestamp[0..3]}#{random_number ? random_number[4..11] : gen_64bit_id[4..11]}")
70
+ end
71
+ end
72
+
73
+ # Checks if the previously created nonce was already used.
74
+ # === Parameters
75
+ # * nonce = Nonce string
76
+ def get_nonce(nonce, options = {})
77
+ response = get_response(:post, "/nonce/#{nonce}", {}, true)
78
+ (response_ok?(response) and response.parsed_response["status"]) ? true : handle_exception(response.parsed_response["errorCode"], response.parsed_response["message"])
79
+ end
80
+
81
+ # Generates a 64bit random number as part of the nonce.
82
+ def gen_64bit_id
83
+ encode_id(rand(18446744073709551615))
84
+ end
85
+
86
+ private
87
+ # The second part of the challenge response authentification method.
88
+ # === Parameters
89
+ # * sid = Session Id
90
+ # * uid = User
91
+ # * password = User password
92
+ # * salt = Salt delivered by SessionVOC
93
+ # * hash = Hash delivered by SessionVOC
94
+ # * options
95
+ def challenge_response(sid, uid, password, salt, hash, options = {})
96
+ logger.debug("Authentification#challenge_response for sid: #{sid} for uid: #{uid}")
97
+ response = get_response(:put, "/session/#{sid}/authenticate",
98
+ {:body => {'uid' => uid, 'password' => encrypt_password(password, salt, hash)}.to_json})
99
+ if response_ok?(response)
100
+ response.parsed_response
101
+ else
102
+ if options[:no_exception]
103
+ false
104
+ else
105
+ handle_exception(response.parsed_response["errorCode"], response.parsed_response["message"])
106
+ end
107
+ end
108
+ end
109
+
110
+ # Creates an encrypted and hashed password as required by the SessionVOC server.
111
+ # === Parameters
112
+ # * password = Password
113
+ # * salt = Salt delivered by SessionVOC
114
+ # * hash = Hash delivered by SessionVOC
115
+ def encrypt_password(password, salt, hash)
116
+ if hash.to_i == Sessionvoc::Open::Base::HASH_TYPES[:HASH_NONE]
117
+ return password
118
+ elsif hash.to_i == Sessionvoc::Open::Base::HASH_TYPES[:HASH_SHA1]
119
+ return Digest::MD5.hexdigest(Digest::MD5.hexdigest(Digest::SHA1.hexdigest(password)) + salt)
120
+ elsif hash.to_i == Sessionvoc::Open::Base::HASH_TYPES[:HASH_SHA224]
121
+ raise Sessionvoc::Open::NotSupportedException, "SHA224 is not supported"
122
+ elsif hash.to_i == Sessionvoc::Open::Base::HASH_TYPES[:HASH_SHA256]
123
+ raise Sessionvoc::Open::NotSupportedException, "SHA256 is not supported"
124
+ elsif hash.to_i == Sessionvoc::Open::Base::HASH_TYPES[:HASH_MD5]
125
+ return Digest::MD5.hexdigest(Digest::MD5.hexdigest(password) + salt)
126
+ end
127
+ end
128
+
129
+ # Encodes the 64bit random number.
130
+ # === Parameters
131
+ # * n = Random number
132
+ def encode_id(n)
133
+ n.to_s(36).rjust(13, '0')
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,169 @@
1
+ # Copyright:: 2011 triAGENS GmbH
2
+ # Author:: Oliver Kiessler (mailto:kiessler@inceedo.com)
3
+ module Sessionvoc
4
+ module Open
5
+ # This Ruby library provides a Ruby on Rails session store for the SessionVOC. It can also be used outside of Rails to interact with SessionVOC
6
+ # directly or within Sinatra and other webapplication frameworks in Ruby.
7
+ #
8
+ # The SessionVOC is a noSQL database optimized for the management of user sessions. Additionally the SessionVOC establishes security mechanisms
9
+ # that are difficult to implement with other session management systems. It depends on the actual scenario which of these functions
10
+ # has the highest priority.
11
+ class Base
12
+ VERSION = '1.7.3'
13
+ attr_accessor :configuration, :logger
14
+
15
+ # SessionVOC Error Codes used by the server.
16
+ ERROR_CODES = {
17
+ 1 => "Errorcode 1: Internal Server Error",
18
+ 2 => "Errorcode 2: Illegal request",
19
+ 3 => "Errorcode 3: Unknown session identifier",
20
+ 4 => "Errorcode 4: Authentification failed",
21
+ 5 => "Errorcode 5: Unknown form data",
22
+ 6 => "Errorcode 6: Temporary error",
23
+ 7 => "Errorcode 7: Illegal authentification request",
24
+ 8 => "Errorcode 8: Illegal user identifier",
25
+ 9 => "Errorcode 9: Session remove (delete) failure",
26
+ 10 => "Errorcode 10: Get request failure, can not output",
27
+ 11 => "Errorcode 11: Parser error, illegal Json",
28
+ 12 => "Errorcode 12: Error while attempting to create form data, internet server error",
29
+ 13 => "Errorcode 13: Error while attempting to delete form data, invalid form identifier or internal server error",
30
+ 14 => "Errorcode 14: Error while attempting to modify form data, invalid form identifier",
31
+ 15 => "Errorcode 15: Error while attempting to output form data, invalid form identifier",
32
+ 16 => "Errorcode 16: Internal Server Error while attempting to remove form data",
33
+ 17 => "Errorcode 17: Error while attempting to update form data, invalid session or form identifier"
34
+ }
35
+
36
+ # Existing hash types used by the server.
37
+ HASH_TYPES = {
38
+ :HASH_NONE => 0,
39
+ :HASH_SHA1 => 1,
40
+ :HASH_SHA224 => 2,
41
+ :HASH_SHA256 => 3,
42
+ :HASH_MD5 => 4
43
+ }
44
+
45
+ # Existing data types used by the server.
46
+ DATA_TYPES = {
47
+ 0 => :DT_NULL,
48
+ 1 => :DT_BOOLEAN,
49
+ 2 => :DT_CHAR,
50
+ 3 => :DT_DOUBLE,
51
+ 4 => :DT_FLOAT,
52
+ 5 => :DT_INTEGER,
53
+ 6 => :DT_LONG_INTEGER,
54
+ 7 => :DT_STRING,
55
+ 8 => :DT_UNSIGNED_INTEGER,
56
+ 9 => :DT_UNSIGNED_LONG_INTEGER,
57
+ 10 => :DT_BLOB,
58
+ 11 => :DT_VARIANT,
59
+ -1 => :DT_UNDEFINED
60
+ }
61
+
62
+ # Existing access types allowed by the server.
63
+ DATA_ACCESS = {
64
+ 0 => :DA_READ_ONLY,
65
+ 1 => :DA_READ_WRITE,
66
+ 2 => :DA_WRITE_ONLY,
67
+ -1 => :DA_UNDEFINED
68
+ }
69
+
70
+ # === Parameters
71
+ # * host = Hostname (required)
72
+ # * port = Port (required)
73
+ # * protocol = "http"/"https", defaults to "http"
74
+ # * log_level = defaults to LOGGER::INFO
75
+ # * auth = Authentication method configured in SessionVOC server
76
+ def initialize(options = {})
77
+ default_options = {'host' => 'localhost', 'port' => '8208', 'protocol' => 'http', 'log_level' => Logger::INFO,
78
+ 'strict_mode' => true, 'auth' => 'none'}
79
+ options = default_options.merge(options)
80
+ options.empty? ? read_configuration : self.configuration = Sessionvoc::Open::Configuration.new(options)
81
+ if defined?(Rails)
82
+ self.logger = Rails.logger
83
+ else
84
+ self.logger = Logger.new(STDOUT)
85
+ self.logger.level = options['log_level']
86
+ end
87
+ self.logger.info("SessionVOC Open Ruby Client Version: #{VERSION}")
88
+ end
89
+
90
+ protected
91
+ # Reads the configuration from the current directory and when it doesn't find it falls back to the
92
+ # global configuration in the home directory (~/.sessionvoc.yml) under which this ruby interpreter is run.
93
+ def read_configuration
94
+ if File.exists?("config.yml")
95
+ logger.info("Using config in this directory")
96
+ self.configuration = Sessionvoc::Open::Configuration.new(YAML.load(File.read("config.yml")))
97
+ else
98
+ begin
99
+ logger.info("Using config in your home directory")
100
+ self.configuration = Sessionvoc::Open::Configuration.new(YAML.load(File.read("#{ENV['HOME']}/.sessionvoc.yml")))
101
+ rescue Errno::ENOENT
102
+ raise Sessionvoc::Open::ConfigurationMissingException, "config.yml expected in current directory or ~/.sessionvoc.yml"
103
+ end
104
+ end
105
+ end
106
+
107
+ # Returns the HTTP base URL for the SessionVOC Rest Service.
108
+ def base_url
109
+ "#{self.configuration.options['protocol']}://#{self.configuration.options['host']}:#{self.configuration.options['port']}"
110
+ end
111
+
112
+ # Returns true/false whether to use the client in strict-mode or not.
113
+ # In strict-mode exceptions are raised for the most common client operations.
114
+ def use_strict_mode?
115
+ self.configuration.options['strict_mode']
116
+ end
117
+
118
+ # Convenience method to access error codes from within this instance.
119
+ def codes; ERROR_CODES; end
120
+
121
+ # Internal method used to handle internal exceptions and map the error code to a human
122
+ # readable message.
123
+ # === Parameters
124
+ # * errorCode = Errorcode number
125
+ # * message = Optional message
126
+ def handle_exception(errorCode, message = nil)
127
+ case errorCode
128
+ when 1
129
+ raise Sessionvoc::Open::InternalServerErrorException, display_message(errorCode, message)
130
+ when 2
131
+ raise Sessionvoc::Open::IllegalRequestException, display_message(errorCode, message)
132
+ when 3
133
+ raise Sessionvoc::Open::InvalidSidException, display_message(errorCode, message)
134
+ when 4
135
+ raise Sessionvoc::Open::AuthentificationFailedException, display_message(errorCode, message)
136
+ when 5
137
+ raise Sessionvoc::Open::InvalidFidException, display_message(errorCode, message)
138
+ when 6
139
+ raise Sessionvoc::Open::TemporaryErrorException, display_message(errorCode, message)
140
+ when 7
141
+ raise Sessionvoc::Open::IllegalAuthentificationRequestException, display_message(errorCode, message)
142
+ when 8
143
+ raise Sessionvoc::Open::IllegalUserIdentifierException, display_message(errorCode, message)
144
+ when 9
145
+ raise Sessionvoc::Open::SessionDeletionFailure, display_message(errorCode, message)
146
+ when 11
147
+ raise Sessionvoc::Open::InvalidJSONException, display_message(errorCode, message)
148
+ when 12
149
+ raise Sessionvoc::Open::InternalServerErrorException, display_message(errorCode, message)
150
+ when 13
151
+ raise Sessionvoc::Open::InvalidFidException, display_message(errorCode, message)
152
+ when 14
153
+ raise Sessionvoc::Open::FormDataCantBeModifiedException, display_message(errorCode, message)
154
+ when 15
155
+ raise Sessionvoc::Open::InvalidFidException, display_message(errorCode, message)
156
+ when 16
157
+ raise Sessionvoc::Open::FormDataCantBeDestroyedException, display_message(errorCode, message)
158
+ when 17
159
+ raise Sessionvoc::Open::InvalidSidException, display_message(errorCode, message)
160
+ end
161
+ end
162
+
163
+ # Displays a message if available.
164
+ def display_message(errorCode, message)
165
+ message ? "#{codes[errorCode]} - #{message}" : codes[errorCode]
166
+ end
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,54 @@
1
+ # Copyright:: 2011 triAGENS GmbH
2
+ # Author:: Oliver Kiessler (mailto:kiessler@inceedo.com)
3
+ module Sessionvoc
4
+ module Open
5
+ # You can setup the client by passing options into the constructor or by using a configuration file in YAML.
6
+ # client = Sessionvoc::Open::Client.new('host' => 'localhost', 'port' => 12345, 'auth' => 'challenge')
7
+ class Client < Sessionvoc::Open::Base
8
+ include Sessionvoc::Open::Session
9
+ include Sessionvoc::Open::Authentification
10
+ include Sessionvoc::Open::FormData
11
+ include Sessionvoc::Open::DataConversion
12
+ include Sessionvoc::Open::MetaData
13
+
14
+ private
15
+ # Internal method to create the httparty request and return the http response that is already parsed by httparty.
16
+ # === Parameters
17
+ # * http_verb = HTTP verb symbol (:get, :post, :put, :delete)
18
+ # * endpoint = HTTP URL endpoint
19
+ # * options = Optional options with body and headers
20
+ def get_response(http_verb, endpoint, options = {}, no_uri_escape = false)
21
+ begin
22
+ if no_uri_escape
23
+ endpoint_value = "#{base_url}#{endpoint}"
24
+ else
25
+ endpoint_value = "#{base_url}#{URI.escape(endpoint)}"
26
+ end
27
+ logger.debug "Using endpoint: #{endpoint_value}"
28
+ body = {}; body.merge!(options) if options and options.any?
29
+ response = (http_verb == :post or http_verb == :put) ? HTTParty.send(http_verb, endpoint_value, body) : HTTParty.send(http_verb, endpoint_value)
30
+ logger.debug "Response: #{response.inspect}" if response
31
+ response
32
+ rescue => e
33
+ logger.error("Could not connect to SessionVOC: #{e.message}")
34
+ raise Sessionvoc::Open::ConnectionRefusedException
35
+ end
36
+ end
37
+
38
+ # Internal method to check the statuscode of a response and its error message if available.
39
+ # === Parameters
40
+ # * response = HTTP response
41
+ def response_ok?(response)
42
+ response and response.parsed_response and response.response.is_a?(Net::HTTPOK) and not response.parsed_response["error"]
43
+ end
44
+
45
+ # Internal method to get meta data about the types and access permissions for the given SessionVOC server installation.
46
+ def meta_data
47
+ logger.debug("Client#meta_data")
48
+ @@meta_data ||= nil
49
+ @@meta_data = self.datainfo unless @@meta_data
50
+ @@meta_data
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,24 @@
1
+ # Copyright:: 2011 triAGENS GmbH
2
+ # Author:: Oliver Kiessler (mailto:kiessler@inceedo.com)
3
+ module Sessionvoc
4
+ module Open
5
+ # You can also provide a configuration file in the current directory of your client to avoid setting up the client within your code.
6
+ # Checkout the config.yml.sample file as an example.
7
+ #
8
+ # If this configuration file isn't found in the current directory, the client will look for a "global" configuration file in your
9
+ # home directory (~/.sessionvoc.yml).
10
+ class Configuration
11
+ attr_accessor :options
12
+
13
+ # === Parameters
14
+ # * host = Hostname (required)
15
+ # * port = Port (required)
16
+ # * protocol = "http"/"https", defaults to "http"
17
+ # * auth = "none"/"simple"/"challenge"
18
+ # * log_level = defaults to LOGGER::INFO
19
+ def initialize(options = {})
20
+ self.options = options
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,100 @@
1
+ # Copyright:: 2011 triAGENS GmbH
2
+ # Author:: Oliver Kiessler (mailto:kiessler@inceedo.com)
3
+ module Sessionvoc
4
+ module Open
5
+ # The methods in this module handle the data conversion and access permissions expected by the SessionVOC
6
+ # server.
7
+ module DataConversion
8
+ # Forces session values to conform the data type the SessionVOC server expects. This method also
9
+ # checks if the value is allowed to be changed.
10
+ # === Parameters
11
+ # * scope = "transData","userData","formData"
12
+ # * key = Key
13
+ # * value = Value
14
+ # * session_data = Session object to work on
15
+ def enforce_value_type(scope, key, value, session_data)
16
+ if session_data and session_data[scope] and session_data['sid']
17
+ meta_data = datainfo
18
+ scope = "form" if scope == "formData"
19
+ if meta_data and meta_data["access"] and meta_data["access"][scope] and [1, 2].include?(meta_data["access"][scope][key.to_s])
20
+ if meta_data["types"] and meta_data["types"][scope] and meta_data["types"][scope][key.to_s]
21
+ desired_type = Sessionvoc::Open::Base::DATA_TYPES[meta_data["types"][scope][key.to_s]]
22
+ if desired_type
23
+ session_data[scope][key] = convert(key, value, desired_type)
24
+ else
25
+ raise Sessionvoc::Open::DataConversionException, "Unknown type"
26
+ end
27
+ else
28
+ raise Sessionvoc::Open::DataConversionException, "Unknown attribute"
29
+ end
30
+ else
31
+ raise Sessionvoc::Open::DataConversionException, "Scope '#{scope}' not found"
32
+ end
33
+ return session_data
34
+ else
35
+ if session_data.nil? or (session_data['sid'].nil? or session_data['sid'].blank?)
36
+ raise Sessionvoc::Open::InvalidSidException
37
+ else
38
+ raise Sessionvoc::Open::DataConversionException
39
+ end
40
+ end
41
+ end
42
+
43
+ # Sets the meta data used by the SessionVOC server.
44
+ def datainfo
45
+ @@meta_data ||= nil
46
+ @@meta_data = ActionDispatch::Session::SessionvocStore::Session.client.datainfo unless @@meta_data
47
+ @@meta_data
48
+ end
49
+
50
+ # This methods performs the actual type conversion.
51
+ # === Parameters
52
+ # * key = Key
53
+ # * value = value
54
+ # * desired_type = Type expected by SessionVOC server
55
+ def convert(key, value, desired_type)
56
+ if key and value and desired_type
57
+ begin
58
+ case desired_type
59
+ when :DT_NULL
60
+ value = nil
61
+ when :DT_BOOLEAN
62
+ if value == "true" or value == "1"
63
+ value = true
64
+ elsif value == "false" or value == "0"
65
+ value = false
66
+ else
67
+ value = false
68
+ end
69
+ when :DT_CHAR
70
+ value = value.to_s[0..0]
71
+ when :DT_STRING
72
+ value = value.to_s
73
+ when :DT_DOUBLE
74
+ value = value.to_f
75
+ when :DT_FLOAT
76
+ value = value.to_f
77
+ when :DT_INTEGER
78
+ value = value.to_i
79
+ when :DT_LONG_INTEGER
80
+ value = value.to_i
81
+ when :DT_UNSIGNED_INTEGER
82
+ value = value.to_i
83
+ value = nil if value < 0
84
+ when :DT_UNSIGNED_LONG_INTEGER
85
+ value = value.to_i
86
+ value = nil if value < 0
87
+ when :DT_BLOB
88
+ value = Base64.encode64(value)
89
+ else
90
+ value = value.to_s
91
+ end
92
+ rescue => e
93
+ return nil
94
+ end
95
+ value
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end