dse-driver 1.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.
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Copyright (c) 2006-2016 Apple Inc. All rights reserved.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ **/
16
+
17
+ #include <gssapi/gssapi.h>
18
+ #include <gssapi/gssapi_generic.h>
19
+ #include <gssapi/gssapi_krb5.h>
20
+
21
+ #define krb5_get_err_text(context,code) error_message(code)
22
+
23
+ #define AUTH_GSS_ERROR -1
24
+ #define AUTH_GSS_COMPLETE 1
25
+ #define AUTH_GSS_CONTINUE 0
26
+
27
+ #define GSS_AUTH_P_NONE 1
28
+ #define GSS_AUTH_P_INTEGRITY 2
29
+ #define GSS_AUTH_P_PRIVACY 4
30
+
31
+ typedef struct {
32
+ gss_ctx_id_t context;
33
+ gss_name_t server_name;
34
+ gss_OID mech_oid;
35
+ long int gss_flags;
36
+ gss_cred_id_t client_creds;
37
+ char* username;
38
+ char* response;
39
+ int responseLen;
40
+ int responseConf;
41
+ } gss_client_state;
42
+
43
+ typedef struct {
44
+ gss_ctx_id_t context;
45
+ gss_name_t server_name;
46
+ gss_name_t client_name;
47
+ gss_cred_id_t server_creds;
48
+ gss_cred_id_t client_creds;
49
+ char* username;
50
+ char* targetname;
51
+ char* response;
52
+ char* ccname;
53
+ } gss_server_state;
54
+
55
+ void authenticate_gss_client_init(
56
+ const char* service, const char* principal, const char* ticket_cache, long int gss_flags,
57
+ gss_server_state* delegatestate, gss_OID mech_oid, gss_client_state* state
58
+ );
59
+ int authenticate_gss_client_clean(
60
+ gss_client_state *state
61
+ );
62
+ int authenticate_gss_client_step(
63
+ gss_client_state *state, const char *challenge, int challenge_len
64
+ );
65
+ int authenticate_gss_client_unwrap(
66
+ gss_client_state* state, const char* challenge, int challenge_len
67
+ );
68
+ int authenticate_gss_client_wrap(
69
+ gss_client_state* state, const char* challenge, int challenge_len
70
+ );
71
+ void clear_response(gss_client_state* state);
data/lib/dse.rb ADDED
@@ -0,0 +1,104 @@
1
+ # encoding: utf-8
2
+
3
+ #--
4
+ # Copyright (C) 2016 DataStax Inc.
5
+ #
6
+ # This software can be used solely with DataStax Enterprise. Please consult the license at
7
+ # http://www.datastax.com/terms/datastax-dse-driver-license-terms
8
+ #++
9
+
10
+ require 'json'
11
+
12
+ if RUBY_ENGINE == 'jruby'
13
+ require 'challenge_evaluator'
14
+ else
15
+ require 'gss_api_context'
16
+ end
17
+
18
+ require 'cassandra'
19
+
20
+ module Dse
21
+ # Creates a {Dse::Cluster Cluster instance}, which extends {http://dsdocs30/api/cassandra/cluster Cassandra::Cluster}.
22
+ # The API is identical, except that it returns a {Dse::Session Dse::Session} (see below). It takes all of the same
23
+ # options as Cassandra.cluster and the following extra options.
24
+ #
25
+ # @option options [Dse::Graph::Options] :graph_options options for the DSE graph statement handler. Takes
26
+ # priority over other `:graph_*` options specified below.
27
+ # @option options [String] :graph_name name of graph to use in graph statements
28
+ # @option options [String] :graph_source graph traversal source
29
+ # @option options [String] :graph_language language used in graph queries
30
+ # @option options [Cassandra::CONSISTENCIES] :graph_read_consistency read consistency level for graph statements.
31
+ # Overrides the standard statement consistency level
32
+ # @option options [Cassandra::CONSISTENCIES] :graph_write_consistency write consistency level for graph statements.
33
+ # Overrides the standard statement consistency level
34
+ #
35
+ # @example Connecting to localhost
36
+ # cluster = Dse.cluster
37
+ #
38
+ # @example Configuring {Dse::Cluster}
39
+ # cluster = Dse.cluster(
40
+ # username: username,
41
+ # password: password,
42
+ # hosts: ['10.0.1.1', '10.0.1.2', '10.0.1.3']
43
+ # )
44
+ #
45
+ # @return [Dse::Cluster] a cluster instance
46
+ def self.cluster(options = {})
47
+ cluster_async(options).get
48
+ end
49
+
50
+ # Creates a {Dse::Cluster Cluster instance}.
51
+ #
52
+ # @see Dse.cluster
53
+ #
54
+ # @return [Cassandra::Future<Dse::Cluster>] a future resolving to the
55
+ # cluster instance.
56
+ def self.cluster_async(options = {})
57
+ graph_options = if !options[:graph_options].nil?
58
+ Cassandra::Util.assert_instance_of(Dse::Graph::Options, options[:graph_options])
59
+ options[:graph_options]
60
+ else
61
+ Dse::Graph::Options.new(options)
62
+ end
63
+ username = options[:username]
64
+ password = options[:password]
65
+ options[:custom_types] ||= []
66
+ options[:custom_types] << Dse::Geometry::Point << Dse::Geometry::LineString << Dse::Geometry::Polygon
67
+ options, hosts = Cassandra.validate_and_massage_options(options)
68
+
69
+ # Use the DSE plain text authenticator if we have a username and password. The above validation already
70
+ # raises an error if one is given without the other.
71
+ options[:auth_provider] = Auth::Providers::Password.new(username, password) if username && password
72
+ rescue => e
73
+ futures = options.fetch(:futures_factory) { return Cassandra::Future::Error.new(e) }
74
+ futures.error(e)
75
+ else
76
+ options[:cluster_klass] = Dse::Cluster
77
+ driver = ::Cassandra::Driver.new(options)
78
+
79
+ # Wrap the load-balancing policy that we'd otherwise run with, with a host-targeting policy.
80
+ # We do this before driver.connect because driver.connect saves off the policy in the cluster
81
+ # registry and does a few other things.
82
+
83
+ lbp = driver.load_balancing_policy
84
+ driver.load_balancing_policy = Dse::LoadBalancing::Policies::HostTargeting.new(lbp)
85
+ future = driver.connect(hosts)
86
+ future.then do |cluster|
87
+ cluster.graph_options.merge!(graph_options)
88
+ cluster
89
+ end
90
+ end
91
+ end
92
+
93
+ require 'dse/cluster'
94
+ require 'dse/util/endian_buffer'
95
+ require 'dse/geometry/line_string'
96
+ require 'dse/geometry/point'
97
+ require 'dse/geometry/polygon'
98
+ require 'dse/session'
99
+ require 'dse/version'
100
+ require 'dse/graph'
101
+ require 'dse/load_balancing/policies/host_targeting'
102
+ require 'dse/statements'
103
+ require 'dse/auth/providers/gss_api'
104
+ require 'dse/auth/providers/password'
@@ -0,0 +1,160 @@
1
+ # encoding: utf-8
2
+
3
+ #--
4
+ # Copyright (C) 2016 DataStax Inc.
5
+ #
6
+ # This software can be used solely with DataStax Enterprise. Please consult the license at
7
+ # http://www.datastax.com/terms/datastax-dse-driver-license-terms
8
+ #++
9
+
10
+ module Dse
11
+ module Auth
12
+ module Providers
13
+ # Auth provider to authenticate with Kerberos. Whenever the client connects to a DSE node,
14
+ # this provider will perform Kerberos authentication operations with it. By default, the provider
15
+ # takes the ip address of the node and uses `Socket#getnameinfo` to find its name in order to construct
16
+ # the full service address (e.g. service@host).
17
+ #
18
+ # @see #initialize
19
+ class GssApi < Cassandra::Auth::Provider
20
+ # @private
21
+ class NameInfoResolver
22
+ def resolve(host)
23
+ Socket.getnameinfo(['AF_INET', 0, host])[0]
24
+ end
25
+ end
26
+
27
+ # @private
28
+ class NoOpResolver
29
+ def resolve(host)
30
+ host
31
+ end
32
+ end
33
+
34
+ # @private
35
+ class Authenticator
36
+ # Copied from kerberosgss.h
37
+ AUTH_GSS_COMPLETE = 1
38
+
39
+ def initialize(authentication_class, host, service, principal, ticket_cache)
40
+ @authentication_class = authentication_class
41
+ @host = host
42
+ @service = service
43
+ @principal = principal
44
+ @ticket_cache = ticket_cache
45
+
46
+ if RUBY_ENGINE == 'jruby'
47
+ @sasl_client = javax.security.sasl.Sasl.createSaslClient(['GSSAPI'],
48
+ nil,
49
+ service,
50
+ host,
51
+ {javax.security.sasl.Sasl::SERVER_AUTH => 'true',
52
+ javax.security.sasl.Sasl::QOP => 'auth'},
53
+ nil)
54
+ config = Dse::Auth::Providers::ChallengeEvaluator.make_configuration(principal, ticket_cache)
55
+ login = javax.security.auth.login.LoginContext.new('DseClient', nil, nil, config)
56
+ login.login
57
+ @subject = login.getSubject
58
+ else
59
+ @gss_context = GssApiContext.new("#{@service}@#{@host}", @principal, @ticket_cache)
60
+ end
61
+ rescue => e
62
+ raise Cassandra::Errors::AuthenticationError.new(
63
+ "Failed to authenticate: #{e.message}",
64
+ nil,
65
+ nil,
66
+ nil,
67
+ nil,
68
+ nil,
69
+ nil,
70
+ :one,
71
+ 0
72
+ )
73
+ end
74
+
75
+ def initial_response
76
+ @authentication_class == 'com.datastax.bdp.cassandra.auth.DseAuthenticator' ?
77
+ 'GSSAPI' :
78
+ challenge_response('GSSAPI-START')
79
+ end
80
+
81
+ if RUBY_ENGINE == 'jruby'
82
+ def challenge_response(token)
83
+ if token == 'GSSAPI-START'
84
+ return '' unless @sasl_client.hasInitialResponse
85
+ token = ''
86
+ end
87
+
88
+ Dse::Auth::Providers::ChallengeEvaluator.evaluate(@sasl_client, @subject, token)
89
+ end
90
+ else
91
+ def challenge_response(token)
92
+ if token == 'GSSAPI-START'
93
+ response = @gss_context.step('')[1]
94
+ elsif !@is_gss_complete
95
+ # Process the challenge as a next step in authentication until we have gotten
96
+ # AUTH_GSS_COMPLETE.
97
+ rc, response = @gss_context.step(token)
98
+ @is_gss_complete = true if rc == AUTH_GSS_COMPLETE
99
+ response ||= ''
100
+ else
101
+ # Ok, we went through all initial phases of auth and now the server is giving us a message
102
+ # to decode.
103
+ data = @gss_context.unwrap(token)
104
+
105
+ raise 'Bad response from server' if data.length != 4
106
+ parsed = data.unpack('>L').first
107
+ max_length = [parsed & 0xffffff, 65536].min
108
+
109
+ # Set up a response like this:
110
+ # byte 0: the selected qop. 1==auth
111
+ # byte 1-3: the max length for any buffer sent back and forth on this connection. (big endian)
112
+ # the rest of the buffer: the authorization user name in UTF-8 - not null terminated.
113
+
114
+ user_name = @gss_context.user_name
115
+ out = [max_length | 1 << 24].pack('>L') + user_name
116
+ response = @gss_context.wrap(out)
117
+ end
118
+ response
119
+ end
120
+ end
121
+
122
+ def authentication_successful(token)
123
+ end
124
+ end
125
+
126
+ # @param service [String] name of the kerberos service; defaults to 'dse'.
127
+ # @param host_resolver [Boolean, Object] whether to use a host-resolver. By default,
128
+ # `Socket#getnameinfo` is used. To disable host-resolution, specify a `false` value. You may also
129
+ # provide a custom resolver, which is an object that implements the `resolve(host_ip)` method.
130
+ # @param principal [String] The principal whose cached credentials are used to authenticate. Defaults
131
+ # to the first principal stored in the ticket cache.
132
+ # @param ticket_cache [String] The ticket cache containing the cached credential we seek. Defaults
133
+ # *on Linux* to /tmp/krb5cc_&lt;uid&gt; (where uid is the numeric uid of the user running the
134
+ # client program). In MRI only, the `KRB5CCNAME` environment variable supercedes this. On Mac,
135
+ # the default is a symbolic reference to a ticket-cache server process.
136
+ def initialize(service = 'dse', host_resolver = true, principal = nil, ticket_cache = nil)
137
+ @service = service
138
+ @host_resolver = case host_resolver
139
+ when false
140
+ NoOpResolver.new
141
+ when true
142
+ NameInfoResolver.new
143
+ else
144
+ host_resolver
145
+ end
146
+ Cassandra::Util.assert_responds_to(:resolve, @host_resolver,
147
+ 'invalid host_resolver: it must have the :resolve method')
148
+ @principal = principal
149
+ @ticket_cache = ticket_cache
150
+ end
151
+
152
+ # @private
153
+ def create_authenticator(authentication_class, host)
154
+ Authenticator.new(authentication_class, @host_resolver.resolve(host.ip.to_s),
155
+ @service, @principal, @ticket_cache)
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,56 @@
1
+ # encoding: utf-8
2
+
3
+ #--
4
+ # Copyright (C) 2016 DataStax Inc.
5
+ #
6
+ # This software can be used solely with DataStax Enterprise. Please consult the license at
7
+ # http://www.datastax.com/terms/datastax-dse-driver-license-terms
8
+ #++
9
+
10
+ module Dse
11
+ module Auth
12
+ module Providers
13
+ # Auth provider to authenticate with username/password for DSE's built-in authentication as well as LDAP.
14
+ #
15
+ # @note No need to instantiate this class manually, use `:username` and
16
+ # `:password` options when calling {Dse.cluster} and one will be
17
+ # created automatically for you.
18
+
19
+ class Password < Cassandra::Auth::Provider
20
+ # @private
21
+ class Authenticator
22
+ def initialize(authentication_class, username, password)
23
+ @authentication_class = authentication_class
24
+ @username = username
25
+ @password = password
26
+ end
27
+
28
+ def initial_response
29
+ @authentication_class == 'com.datastax.bdp.cassandra.auth.DseAuthenticator' ?
30
+ 'PLAIN' :
31
+ challenge_response('PLAIN-START')
32
+ end
33
+
34
+ def challenge_response(token)
35
+ "\x00#{@username}\x00#{@password}"
36
+ end
37
+
38
+ def authentication_successful(token)
39
+ end
40
+ end
41
+
42
+ # @param username [String] username to use for authentication to Cassandra
43
+ # @param password [String] password to use for authentication to Cassandra
44
+ def initialize(username, password)
45
+ @username = username
46
+ @password = password
47
+ end
48
+
49
+ # @private
50
+ def create_authenticator(authentication_class, host)
51
+ Authenticator.new(authentication_class, @username, @password)
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,99 @@
1
+ # encoding: utf-8
2
+
3
+ #--
4
+ # Copyright (C) 2016 DataStax Inc.
5
+ #
6
+ # This software can be used solely with DataStax Enterprise. Please consult the license at
7
+ # http://www.datastax.com/terms/datastax-dse-driver-license-terms
8
+ #++
9
+
10
+ module Dse
11
+ # Cluster represents a DSE cluster. It serves as a {Dse::Session session factory} and a collection of metadata.
12
+ # It wraps a {http://dsdocs30/api/cassandra/cluster Cassandra::Cluster} and exposes all of its functionality.
13
+ class Cluster
14
+ # @return [Dse::Graph::Options] default graph options used by queries on this cluster.
15
+ attr_reader :graph_options
16
+
17
+ # @private
18
+ def initialize(logger,
19
+ io_reactor,
20
+ executor,
21
+ control_connection,
22
+ cluster_registry,
23
+ cluster_schema,
24
+ cluster_metadata,
25
+ execution_options,
26
+ connection_options,
27
+ load_balancing_policy,
28
+ reconnection_policy,
29
+ retry_policy,
30
+ address_resolution_policy,
31
+ connector,
32
+ futures_factory,
33
+ timestamp_generator)
34
+ @delegate_cluster = Cassandra::Cluster.new(logger,
35
+ io_reactor,
36
+ executor,
37
+ control_connection,
38
+ cluster_registry,
39
+ cluster_schema,
40
+ cluster_metadata,
41
+ execution_options,
42
+ connection_options,
43
+ load_balancing_policy,
44
+ reconnection_policy,
45
+ retry_policy,
46
+ address_resolution_policy,
47
+ connector,
48
+ futures_factory,
49
+ timestamp_generator)
50
+ @graph_options = Dse::Graph::Options.new
51
+
52
+ # We need the futures factory ourselves for async error reporting and potentially for our
53
+ # own async processing independent of the C* driver.
54
+ @futures = futures_factory
55
+ end
56
+
57
+ # Delegates to {http://docs.datastax.com/en/developer/ruby-driver/3.0/supplemental/api/cassandra/cluster/?local=true&nav=toc#connect_async-instance_method
58
+ # Cassandra::Cluster#connect_async}
59
+ # to connect asynchronously to a cluster, but returns a future that will resolve to a DSE session rather than
60
+ # Cassandra session.
61
+ #
62
+ # @param keyspace [String] optional keyspace to scope session to
63
+ #
64
+ # @return [Cassandra::Future<Dse::Session>]
65
+ def connect_async(keyspace = nil)
66
+ future = @delegate_cluster.connect_async(keyspace)
67
+ # We want to actually return a DSE session upon successful connection.
68
+ future.then do |cassandra_session|
69
+ Dse::Session.new(cassandra_session, @graph_options, @futures)
70
+ end
71
+ end
72
+
73
+ # Synchronous variant of {#connect_async}.
74
+ #
75
+ # @param keyspace [String] optional keyspace to scope the session to
76
+ #
77
+ # @return [Dse::Session]
78
+ def connect(keyspace = nil)
79
+ connect_async(keyspace).get
80
+ end
81
+
82
+ #### The following methods handle arbitrary delegation to the underlying cluster object. ####
83
+ protected
84
+
85
+ # @private
86
+ def method_missing(method_name, *args, &block)
87
+ # If we get here, we don't have a method of our own. Forward the request to the delegate_cluster.
88
+ # If it returns itself, we will coerce the result to return our *self* instead.
89
+
90
+ result = @delegate_cluster.send(method_name, *args, &block)
91
+ (result == @delegate_cluster) ? self : result
92
+ end
93
+
94
+ # @private
95
+ def respond_to?(method, include_private = false)
96
+ super || @delegate_cluster.respond_to?(method, include_private)
97
+ end
98
+ end
99
+ end