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 +4 -4
- data/.gitignore +3 -0
- data/CHANGELOG.md +6 -0
- data/Gemfile +5 -0
- data/lib/conjur/api/roles.rb +33 -1
- data/lib/conjur/graph.rb +194 -0
- data/lib/conjur-api/version.rb +1 -1
- data/spec/api/graph_spec.rb +102 -0
- data/spec/api/roles_spec.rb +74 -3
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e4158ca28a27fd311f677a695b2f551e9f0f7984
|
4
|
+
data.tar.gz: 4acd4da34ae508e0d91260d37ea93f48b20dc315
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b8f8e9e4833429a08b95c04d002c3163e99dd5af85127418ec50b742d7f8ab3da41149bb607816bf44af3b66a1f6c168277f8c81459505dcf617076f137f5d3e
|
7
|
+
data.tar.gz: 184f448ab7eee155ff9b52bcb23b71bbda1e739bf714f04578c90ba69d0049d4359ff5bb7c91a2f9bde99755347e7741bc86fdc8ec84da2ff5a0406f4cf2cf5b
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
data/Gemfile
CHANGED
data/lib/conjur/api/roles.rb
CHANGED
@@ -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
|
data/lib/conjur/graph.rb
ADDED
@@ -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
|
data/lib/conjur-api/version.rb
CHANGED
@@ -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
|
data/spec/api/roles_spec.rb
CHANGED
@@ -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
|
-
|
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.
|
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-
|
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
|