dse-driver 1.0.1

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