sessionvoc-open 1.7.3

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,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