conjur-api 4.11.2 → 4.12.0

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