conjur-api 4.11.2 → 4.12.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6c0dbb6eee708c58cc9e04487c2695e4c1c7c152
4
- data.tar.gz: f7e5de492037f8ad72fd058533b8cc643153b823
3
+ metadata.gz: e4158ca28a27fd311f677a695b2f551e9f0f7984
4
+ data.tar.gz: 4acd4da34ae508e0d91260d37ea93f48b20dc315
5
5
  SHA512:
6
- metadata.gz: 1ba62b5de107208b2334e8755bb3ea2ae15fffd630eabecfc97acf0fea6956f9b7c7f7e71bbd3e3cf0c0cbb8cb243a5530e8ad49cc127b0312e7ba552feeb2e9
7
- data.tar.gz: c47e6fa447d74ae465c57fbaeaffc417df0fbde69bbac91582563129c119ac7c62da33626230a34512bf881617e416ecb0102bdf4d8470116d3fa5240133daed
6
+ metadata.gz: b8f8e9e4833429a08b95c04d002c3163e99dd5af85127418ec50b742d7f8ab3da41149bb607816bf44af3b66a1f6c168277f8c81459505dcf617076f137f5d3e
7
+ data.tar.gz: 184f448ab7eee155ff9b52bcb23b71bbda1e739bf714f04578c90ba69d0049d4359ff5bb7c91a2f9bde99755347e7741bc86fdc8ec84da2ff5a0406f4cf2cf5b
data/.gitignore CHANGED
@@ -20,3 +20,6 @@ tmp
20
20
  .kateproject.d
21
21
  .rvmrc
22
22
  .idea
23
+
24
+ # rspec
25
+ .rspec
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ # v4.12.0
2
+
3
+ * Add the API method `role_graph` for retrieving role relationships in bulk
4
+
5
+ # v4.11.2
6
+
1
7
  * Patch rest-client's patch of mime-types to support lazy loading
2
8
  * Remove 'wrong' dependency for faster loading
3
9
 
data/Gemfile CHANGED
@@ -4,3 +4,8 @@ source 'https://rubygems.org'
4
4
 
5
5
  # Specify your gem's dependencies in conjur-api.gemspec
6
6
  gemspec
7
+
8
+ group :development do
9
+ gem 'pry'
10
+ end
11
+
@@ -19,9 +19,32 @@
19
19
  # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
20
  #
21
21
  require 'conjur/role'
22
+ require 'conjur/graph'
22
23
 
23
24
  module Conjur
24
25
  class API
26
+ ##
27
+ # Fetch a digraph (or a forest of digraphs) representing of
28
+ # role memberships related transitively to any of a list of roles.
29
+ #
30
+ # @param [Array<Conjur::Role, String>] roles the digraph (or forest thereof) of
31
+ # the ancestors and descendants of these roles or role ids will be returned
32
+ # @param [Hash] options options determining the graph returned
33
+ # @option opts [Boolean] :ancestors Whether to return ancestors of the given roles (true by default)
34
+ # @option opts [Boolean] :descendants Whether to return descendants of the given roles (true by default)
35
+ # @option opts [Conjur::Role, String] :as_role Only roles visible to this role will be included in the graph
36
+ # @return [Conjur::Graph] An object representing the role memberships digraph
37
+ def role_graph roles, options = {}
38
+ roles.map!{|r| normalize_roleid(r) }
39
+ options[:as_role] = normalize_roleid(options[:as_role]) if options.include?(:as_role)
40
+ options.reverse_merge! as_role: normalize_roleid(current_role), descendants: true, ancestors: true
41
+
42
+ query = {from_role: options.delete(:as_role)}
43
+ .merge(options.slice(:ancestors, :descendants))
44
+ .merge(roles: roles).to_query
45
+ Conjur::Graph.new RestClient::Resource.new(Conjur::Authz::API.host, credentials)["#{Conjur.account}/roles?#{query}"].get
46
+ end
47
+
25
48
  def create_role(role, options = {})
26
49
  role(role).tap do |r|
27
50
  r.create(options)
@@ -39,7 +62,7 @@ module Conjur
39
62
  def role_from_username username
40
63
  role(role_name_from_username username)
41
64
  end
42
-
65
+
43
66
  def role_name_from_username username = self.username
44
67
  tokens = username.split('/')
45
68
  if tokens.size == 1
@@ -48,5 +71,14 @@ module Conjur
48
71
  [ tokens[0], tokens[1..-1].join('/') ].join(':')
49
72
  end
50
73
  end
74
+
75
+ private
76
+ def normalize_roleid role
77
+ case role
78
+ when String then role
79
+ when Role then role.roleid
80
+ else raise "Can't normalize #{role}@#{role.class}"
81
+ end
82
+ end
51
83
  end
52
84
  end
@@ -0,0 +1,194 @@
1
+ #
2
+ # Copyright (C) 2015 Conjur Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
5
+ # this software and associated documentation files (the "Software"), to deal in
6
+ # the Software without restriction, including without limitation the rights to
7
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8
+ # the Software, and to permit persons to whom the Software is furnished to do so,
9
+ # subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all
12
+ # copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+ #
21
+ module Conjur
22
+ class Graph
23
+
24
+ include Enumerable
25
+
26
+ # @!attribute r edges
27
+ # @return [Array<Conjur::Graph::Edge>] the edges of this graph
28
+ attr_reader :edges
29
+
30
+ # @api private
31
+ def initialize val
32
+ @edges = case val
33
+ when String then JSON.parse(val)['graph']
34
+ when Hash then val['graph']
35
+ when Array then val
36
+ when Graph then val.edges
37
+ else raise ArgumentError, "don't know how to turn #{val}:#{val.class} into a Graph"
38
+ end.map{|pair| Edge.new(*pair) }.freeze
39
+ @next_node_id = 0
40
+ @node_ids = Hash.new{ |h,k| h[k] = next_node_id }
41
+ end
42
+
43
+ # Enumerates the edges of this graph.
44
+ # @yieldparam [Conjur::Graph::Edge] each edge of the graph
45
+ # @return edge [Conjur::Graph] this graph
46
+ def each_edge
47
+ return enum_for(__method__) unless block_given?
48
+ edges.each{|e| yield e}
49
+ self
50
+ end
51
+
52
+ alias each each_edge
53
+
54
+ # Enumerates the vertices (roles) of this graph
55
+ # @yieldparam vertex [Conjur::Role] each vertex in this graph
56
+ # @return [Conjur::Graph] this graph
57
+ def each_vertex
58
+ return enum_for(__method__) unless block_given?
59
+ vertices.each{|v| yield v}
60
+ end
61
+
62
+
63
+ def to_json short = false
64
+ as_json(short).to_json
65
+ end
66
+
67
+ def as_json short = false
68
+ edges = self.edges.map{|e| e.as_json(short)}
69
+ short ? edges : {'graph' => edges}
70
+ end
71
+
72
+ # @param [String, NilClass] name to assign to the graph. Usually this can be omitted unless you
73
+ # are writing multiple graphs to a single file. Must be in the ID format specified by
74
+ # http://www.graphviz.org/content/dot-language
75
+ #
76
+ # @return [String] the dot format (used by graphvis, among others) representation of this graph.
77
+ def to_dot name = nil
78
+ dot = "digraph #{name || ''} {"
79
+ vertices.each do |v|
80
+ dot << "\n\t" << dot_node(v)
81
+ end
82
+ edges.each do |e|
83
+ dot << "\n\t" << dot_edge(e)
84
+ end
85
+ dot << "\n}"
86
+ end
87
+
88
+ def vertices
89
+ @vertices ||= edges.inject([]) {|a, pair| a.concat pair.to_a }.uniq
90
+ end
91
+
92
+ alias roles vertices
93
+
94
+ private
95
+
96
+ def node_id_for role
97
+ role = role.id if role.respond_to?(:id)
98
+ @node_ids[role]
99
+ end
100
+
101
+ def next_node_id
102
+ id = @next_node_id
103
+ @next_node_id += 1
104
+ "node_#{id}"
105
+ end
106
+
107
+ def node_label_for role
108
+ role = role.id if role.respond_to? :id
109
+ if single_account?
110
+ role = role.split(':', 2).last
111
+ if single_kind?
112
+ role = role.split(':', 2).last
113
+ end
114
+ end
115
+ role
116
+ end
117
+
118
+ def single_account?
119
+ if @single_account.nil?
120
+ @single_account = roles.map do |role|
121
+ role = role.id if role.respond_to?(:id)
122
+ role.split(':').first
123
+ end.uniq.size == 1
124
+ end
125
+ @single_account
126
+ end
127
+
128
+ def single_kind?
129
+ if @single_kind.nil?
130
+ return @single_kind = false unless single_account?
131
+ @single_kind = roles.map do |role|
132
+ role = role.id if role.respond_to?(:id)
133
+ role.split(':')[1]
134
+ end.uniq.size == 1
135
+ end
136
+ @single_kind
137
+ end
138
+
139
+ def dot_node v
140
+ id = node_id_for v
141
+ label = node_label_for v
142
+ "#{id} [label=\"#{label}\"]"
143
+ end
144
+
145
+ def dot_edge e
146
+ parent_id = node_id_for(e.parent)
147
+ child_id = node_id_for(e.child)
148
+ "#{parent_id} -> #{child_id}"
149
+ end
150
+
151
+ # an edge consisting of a parent & child, both of which are Conjur::Role instances
152
+ class Edge
153
+ attr_reader :parent
154
+ attr_reader :child
155
+
156
+ def initialize parent, child
157
+ @parent = parent
158
+ @child = child
159
+ end
160
+
161
+ def to_json short = false
162
+ as_json(short).to_json
163
+ end
164
+
165
+ def as_json short = false
166
+ short ? to_a : to_h
167
+ end
168
+
169
+ def to_h
170
+ # return string keys to make testing less brittle
171
+ {'parent' => @parent, 'child' => @child}
172
+ end
173
+
174
+ def to_a
175
+ [@parent, @child]
176
+ end
177
+
178
+ def to_s
179
+ "<Edge #{parent.id} --> #{child.id}>"
180
+ end
181
+
182
+ def hash
183
+ @hash ||= to_a.map(&:to_s).hash
184
+ end
185
+
186
+ def == other
187
+ other.kind_of?(self.class) and other.parent == parent and other.child == child
188
+ end
189
+
190
+ alias eql? ==
191
+ end
192
+
193
+ end
194
+ end
@@ -19,6 +19,6 @@
19
19
 
20
20
  module Conjur
21
21
  class API
22
- VERSION = "4.11.2"
22
+ VERSION = "4.12.0"
23
23
  end
24
24
  end
@@ -0,0 +1,102 @@
1
+ require 'spec_helper'
2
+
3
+ describe Conjur::Graph do
4
+ let(:edges){ [
5
+ [ 'a', 'b' ],
6
+ [ 'a', 'c'],
7
+ [ 'c', 'd'],
8
+ ['b', 'd'],
9
+ [ 'd', 'e'],
10
+ # make two connected components
11
+ ['o', 'q'],
12
+ ['x', 'o']
13
+ ]}
14
+ let(:hash_graph){ {'graph' => edges} }
15
+ let(:json_graph){ hash_graph.to_json }
16
+ let(:short_json_graph){ edges.to_json }
17
+ let(:long_edges){ edges.map{|e| {'parent' => e[0], 'child' => e[1]}} }
18
+ let(:long_hash_graph){ {'graph' => long_edges} }
19
+ let(:long_json_graph){ long_hash_graph.to_json }
20
+ let(:edge_objects){ edges.map{|e| Conjur::Graph::Edge.new(*e) }}
21
+
22
+ describe "json methods" do
23
+ subject{described_class.new edges}
24
+ it "converts to long json correctly" do
25
+ expect(subject.to_json).to eq(long_json_graph)
26
+ expect(subject.as_json).to eq(long_hash_graph)
27
+ end
28
+
29
+ it "converts to short json correctly" do
30
+ expect(subject.to_json(true)).to eq(short_json_graph)
31
+ expect(subject.as_json(true)).to eq(edges)
32
+ end
33
+ end
34
+
35
+ describe "#vertices" do
36
+ subject{Conjur::Graph.new(edges).vertices.to_set}
37
+ it "contains all unique members of edges" do
38
+ expect(subject.to_set).to eq(edges.flatten.uniq.to_set)
39
+ end
40
+ end
41
+
42
+ describe "#to_dot" do
43
+ let(:name){ nil }
44
+ subject{ Conjur::Graph.new(edges).to_dot(name) }
45
+ before do
46
+ File.write('/tmp/conjur-graph-spec.dot', subject)
47
+ end
48
+
49
+ let(:role_to_node_id) do
50
+ {}.tap do |h|
51
+ edges.flatten.uniq.each do |v|
52
+ expect(subject =~ /^\s*([a-z][0-9a-z_\-]*)\s*\[label\="(#{v})"\]/i).to be_truthy
53
+ h[$2] = $1
54
+ end
55
+ end
56
+ end
57
+
58
+ context "when given a name" do
59
+ let(:name){ 'foo' }
60
+ it "names the digraph" do
61
+ expect(subject).to match(/\A\s*digraph\s+foo\s*\{/)
62
+ end
63
+ end
64
+
65
+ it "defines all the vertices in the graph" do
66
+ edges.flatten.uniq.each do |v|
67
+ expect(subject).to match(/^\s*[a-z][0-9a-z_\-]*\s*\[label\="#{v}"\]/i)
68
+ end
69
+ end
70
+
71
+ it "defines all the edges in the graph" do
72
+ edges.each do |e|
73
+ parent_id = role_to_node_id[e[0]]
74
+ child_id = role_to_node_id[e[1]]
75
+ expect(subject).to match(/^\s*#{parent_id}\s*\->\s*#{child_id}/)
76
+ end
77
+ end
78
+ end
79
+
80
+ describe "Graph.new" do
81
+ let(:arg){ edges }
82
+ subject{ described_class.new arg }
83
+ def self.it_accepts_the_argument
84
+ it "accepts the argument" do
85
+ expect(subject.edges.to_set).to eq(edge_objects.to_set)
86
+ end
87
+ end
88
+ describe "given an array of edges" do
89
+ it_accepts_the_argument
90
+ end
91
+
92
+ describe "given a hash of {'graph' => <array of edges>}" do
93
+ let(:arg){ hash_graph }
94
+ it_accepts_the_argument
95
+ end
96
+
97
+ describe "given a JSON string" do
98
+ let(:arg){ json_graph }
99
+ it_accepts_the_argument
100
+ end
101
+ end
102
+ end
@@ -1,14 +1,85 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Conjur::API, api: :dummy do
4
+ subject { api }
5
+
6
+ describe 'role_graph' do
7
+ let(:roles){ [ 'acct:user:alice', 'acct:user:bob', 'acct:user:eve' ] }
8
+ let(:options){ {} }
9
+ let(:current_role){ 'some-role' }
10
+ let(:graph){
11
+ [
12
+ [ 'acct:user:alice', 'acct:user:eve' ],
13
+ [ 'acct:user:bob', 'acct:user:eve']
14
+ ]
15
+ }
16
+ let(:response){ {
17
+ graph: graph
18
+ }.to_json }
19
+
20
+ let(:graph_edges){
21
+ graph.map{|e| Conjur::Graph::Edge.new *e}
22
+ }
23
+
24
+ before do
25
+ allow(api).to receive(:current_role).and_return current_role
26
+ end
27
+
28
+ subject{ api.role_graph roles, options }
29
+
30
+ def role_graph_url_for roles, options, current_role
31
+ qs = options.reverse_merge(ancestors: true, descendants: true)
32
+ .merge(from_role: current_role, roles: roles).slice(:from_role, :ancestors, :descendants, :roles).to_query
33
+ "http://authz.example.com/#{account}/roles?#{qs}"
34
+ end
35
+
36
+ def expect_request_with_params params={}
37
+ expect(RestClient::Request).to receive(:execute)
38
+ .with(headers: credentials[:headers], method: :get, url: role_graph_url_for(roles, options, current_role))
39
+ .and_return(response)
40
+ end
41
+
42
+ it "gets /roles with the correct params" do
43
+ expect_request_with_params ancestors: true, descendants: true, from_role: current_role
44
+ subject
45
+ end
46
+
47
+ context "when options[:ancestors] and options[:descendants] are false" do
48
+ let(:options){ { ancestors: false, descendants: false } }
49
+ it "gets /roles with the correct params" do
50
+ expect_request_with_params ancestors: false, descendants: false, from_role: current_role
51
+ subject
52
+ end
53
+ end
54
+
55
+ context "when given options[:as_role] = 'foo'" do
56
+ it "sets the from_role param to 'foo'" do
57
+ expect_request_with_params from_role: 'foo'
58
+ subject
59
+ end
60
+ end
61
+
62
+ describe "the result" do
63
+ it "is a Conjur::Graph" do
64
+ expect_request_with_params
65
+ expect(subject).to be_kind_of(Conjur::Graph)
66
+ end
67
+ it "has the right edges" do
68
+ expect_request_with_params
69
+ expect(subject.edges.to_set).to eq(graph_edges.to_set)
70
+ end
71
+ end
72
+
73
+ end
74
+
4
75
  describe '#role_name_from_username' do
5
- subject { api }
76
+
6
77
  before {
7
78
  allow(api).to receive(:username) { username }
8
79
  }
9
80
  context "username is" do
10
- [
11
- [ 'the-user', 'user:the-user' ],
81
+ [
82
+ [ 'the-user', 'user:the-user' ],
12
83
  [ 'host/the-host', 'host:the-host' ],
13
84
  [ 'host/a/quite/long/host/name', 'host:a/quite/long/host/name' ],
14
85
  [ 'newkind/host/name', 'newkind:host/name' ],
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: conjur-api
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.11.2
4
+ version: 4.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rafal Rzepecki
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-01-22 00:00:00.000000000 Z
12
+ date: 2015-01-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rest-client
@@ -232,6 +232,7 @@ files:
232
232
  - lib/conjur/escape.rb
233
233
  - lib/conjur/event_source.rb
234
234
  - lib/conjur/exists.rb
235
+ - lib/conjur/graph.rb
235
236
  - lib/conjur/group.rb
236
237
  - lib/conjur/has_attributes.rb
237
238
  - lib/conjur/has_id.rb
@@ -254,6 +255,7 @@ files:
254
255
  - lib/conjur/variable.rb
255
256
  - reqspeed.rb
256
257
  - spec/api/authn_spec.rb
258
+ - spec/api/graph_spec.rb
257
259
  - spec/api/groups_spec.rb
258
260
  - spec/api/hosts_spec.rb
259
261
  - spec/api/layer_spec.rb
@@ -317,6 +319,7 @@ test_files:
317
319
  - features/ping_as_server.feature
318
320
  - features/ping_as_user.feature
319
321
  - spec/api/authn_spec.rb
322
+ - spec/api/graph_spec.rb
320
323
  - spec/api/groups_spec.rb
321
324
  - spec/api/hosts_spec.rb
322
325
  - spec/api/layer_spec.rb