admission 0.4.5 → 0.4.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/admission.gemspec +7 -7
- data/lib/admission/version.rb +1 -1
- metadata +5 -48
- data/.gitignore +0 -40
- data/.rspec +0 -2
- data/.ruby-version +0 -1
- data/Gemfile +0 -7
- data/LICENSE +0 -674
- data/README.md +0 -24
- data/bin/build +0 -10
- data/bin/rspec +0 -8
- data/spec/integration/action_arbitrating_spec.rb +0 -119
- data/spec/integration/resource_arbitrating_spec.rb +0 -276
- data/spec/rspec_config.rb +0 -103
- data/spec/spec_helper.rb +0 -28
- data/spec/test_context/country.rb +0 -24
- data/spec/test_context/index.rb +0 -5
- data/spec/test_context/person.rb +0 -31
- data/spec/test_context/persons_fixtures.rb +0 -43
- data/spec/test_context/privileges_and_rules.rb +0 -119
- data/spec/unit/arbitration_spec.rb +0 -33
- data/spec/unit/index_spec.rb +0 -144
- data/spec/unit/privilege/order_definer_spec.rb +0 -184
- data/spec/unit/privilege_spec.rb +0 -178
- data/spec/unit/rails/action_admission_spec.rb +0 -188
- data/spec/unit/rails/controller_addon_spec.rb +0 -68
- data/spec/unit/rails/scope_resolver_spec.rb +0 -72
- data/spec/unit/resource_arbitration_spec.rb +0 -76
- data/spec/unit/status_spec.rb +0 -79
- data/visualisation/.babelrc +0 -7
- data/visualisation/actions/index.js +0 -0
- data/visualisation/components/app_container.jsx +0 -78
- data/visualisation/components/input_with_select.jsx +0 -177
- data/visualisation/components/nested_list_row.jsx +0 -48
- data/visualisation/components/privilege_select.jsx +0 -70
- data/visualisation/components/privileges_panel.jsx +0 -73
- data/visualisation/components/rules_panel.jsx +0 -124
- data/visualisation/dist/.gitkeep +0 -0
- data/visualisation/helpers.js +0 -69
- data/visualisation/index.jsx +0 -89
- data/visualisation/package.json +0 -27
- data/visualisation/reducers/index.js +0 -35
- data/visualisation/server.rb +0 -23
- data/visualisation/style.scss +0 -248
- data/visualisation/webpack.config.js +0 -47
- data/visualisation/yarn.lock +0 -3354
@@ -1,76 +0,0 @@
|
|
1
|
-
require_relative '../spec_helper'
|
2
|
-
|
3
|
-
RSpec.describe Admission::ResourceArbitration do
|
4
|
-
|
5
|
-
describe '#new' do
|
6
|
-
|
7
|
-
it 'parses simple Symbol scope' do
|
8
|
-
arbitration = Admission::ResourceArbitration.new nil, {scope: -1}, :req, :scope
|
9
|
-
expect(arbitration).to have_inst_vars(
|
10
|
-
person: nil,
|
11
|
-
rules_index: -1,
|
12
|
-
request: :req,
|
13
|
-
resource: nil
|
14
|
-
)
|
15
|
-
end
|
16
|
-
|
17
|
-
it 'parses type scope' do
|
18
|
-
resource = Object.new
|
19
|
-
arbitration = Admission::ResourceArbitration.new nil, {objects: -1}, :req, resource
|
20
|
-
expect(arbitration).to have_inst_vars(
|
21
|
-
person: nil,
|
22
|
-
rules_index: -1,
|
23
|
-
request: :req,
|
24
|
-
resource: resource
|
25
|
-
)
|
26
|
-
end
|
27
|
-
|
28
|
-
it 'parses nested type scope' do
|
29
|
-
resource = Object.new
|
30
|
-
arbitration = Admission::ResourceArbitration.new nil, {:'objects:vars' => -1}, :req, [resource, :vars]
|
31
|
-
expect(arbitration).to have_inst_vars(
|
32
|
-
person: nil,
|
33
|
-
rules_index: -1,
|
34
|
-
request: :req,
|
35
|
-
resource: resource
|
36
|
-
)
|
37
|
-
end
|
38
|
-
|
39
|
-
end
|
40
|
-
|
41
|
-
describe 'RulesBuilder' do
|
42
|
-
|
43
|
-
describe 'fails when given reserved action name' do
|
44
|
-
|
45
|
-
let(:builder){
|
46
|
-
builder = Admission::ResourceArbitration::RulesBuilder.new nil
|
47
|
-
builder.instance_variable_set '@privilege', 'privilege'
|
48
|
-
builder
|
49
|
-
}
|
50
|
-
|
51
|
-
it '#allow' do
|
52
|
-
expect{ builder.allow '', :allow }.not_to raise_exception
|
53
|
-
expect{ builder.allow '', Admission::ALL_ACTION }.to(
|
54
|
-
raise_exception("reserved action name #{Admission::ALL_ACTION}")
|
55
|
-
)
|
56
|
-
end
|
57
|
-
|
58
|
-
it '#forbid' do
|
59
|
-
expect{ builder.forbid '', :forbid }.not_to raise_exception
|
60
|
-
expect{ builder.forbid '', Admission::ALL_ACTION }.to(
|
61
|
-
raise_exception("reserved action name #{Admission::ALL_ACTION}")
|
62
|
-
)
|
63
|
-
end
|
64
|
-
|
65
|
-
it '#allow_resource' do
|
66
|
-
expect{ builder.allow_resource :'', :allow_resource, &->{} }.not_to raise_exception
|
67
|
-
expect{ builder.allow_resource :'', Admission::ALL_ACTION, &->{} }.to(
|
68
|
-
raise_exception("reserved action name #{Admission::ALL_ACTION}")
|
69
|
-
)
|
70
|
-
end
|
71
|
-
|
72
|
-
end
|
73
|
-
|
74
|
-
end
|
75
|
-
|
76
|
-
end
|
data/spec/unit/status_spec.rb
DELETED
@@ -1,79 +0,0 @@
|
|
1
|
-
require_relative '../spec_helper'
|
2
|
-
|
3
|
-
RSpec.describe Admission::Status do
|
4
|
-
|
5
|
-
def privilege context
|
6
|
-
@fake_privilege_klass ||= Struct.new(:context, :inherited)
|
7
|
-
@fake_privilege_klass.new context
|
8
|
-
end
|
9
|
-
|
10
|
-
describe '#new' do
|
11
|
-
|
12
|
-
it 'sets privileges to nil' do
|
13
|
-
instance = Admission::Status.new :person, nil, :rules, :arbiter
|
14
|
-
expect(instance).to have_inst_vars(
|
15
|
-
person: :person,
|
16
|
-
privileges: nil,
|
17
|
-
rules: :rules,
|
18
|
-
arbiter: :arbiter
|
19
|
-
)
|
20
|
-
|
21
|
-
instance = Admission::Status.new :person, [], :rules, :arbiter
|
22
|
-
expect(instance).to have_inst_vars(
|
23
|
-
person: :person,
|
24
|
-
privileges: nil,
|
25
|
-
rules: :rules,
|
26
|
-
arbiter: :arbiter
|
27
|
-
)
|
28
|
-
end
|
29
|
-
|
30
|
-
it 'sets privileges and freezes them' do
|
31
|
-
instance = Admission::Status.new :person, [:czech], :rules, :arbiter
|
32
|
-
expect(instance).to have_inst_vars(
|
33
|
-
person: :person,
|
34
|
-
privileges: [:czech],
|
35
|
-
rules: :rules,
|
36
|
-
arbiter: :arbiter
|
37
|
-
)
|
38
|
-
expect(instance.privileges).to be_frozen
|
39
|
-
end
|
40
|
-
|
41
|
-
it 'sorts privileges by context' do
|
42
|
-
instance = Admission::Status.new :person, [
|
43
|
-
privilege(nil),
|
44
|
-
privilege(:czech),
|
45
|
-
privilege(15),
|
46
|
-
privilege(:czech),
|
47
|
-
privilege({a: 15}),
|
48
|
-
privilege({a: {f: 1}}),
|
49
|
-
privilege(nil),
|
50
|
-
privilege({a: 15}),
|
51
|
-
], :rules, :arbiter
|
52
|
-
expect(instance.privileges.map(&:context)).to eq([
|
53
|
-
nil, nil, :czech, :czech, 15, {:a=>15}, {:a=>15}, {:a=>{:f=>1}}
|
54
|
-
])
|
55
|
-
expect(instance.privileges).to be_frozen
|
56
|
-
end
|
57
|
-
|
58
|
-
end
|
59
|
-
|
60
|
-
describe '#allowed_in_contexts' do
|
61
|
-
|
62
|
-
it 'returns empty list for blank privileges' do
|
63
|
-
instance = Admission::Status.new :person, nil, :rules, :arbiter
|
64
|
-
expect(instance.allowed_in_contexts).to eq([])
|
65
|
-
end
|
66
|
-
|
67
|
-
it 'lists only context for which any privilege allows it' do
|
68
|
-
priv1 = privilege text: '1'
|
69
|
-
priv2 = privilege text: '2'
|
70
|
-
rules = {can: {priv1 => true}}
|
71
|
-
instance = Admission::Status.new nil, [priv1, priv2], rules, Admission::Arbitration
|
72
|
-
|
73
|
-
list = instance.allowed_in_contexts :can
|
74
|
-
expect(list).to eq([priv1.context])
|
75
|
-
end
|
76
|
-
|
77
|
-
end
|
78
|
-
|
79
|
-
end
|
data/visualisation/.babelrc
DELETED
File without changes
|
@@ -1,78 +0,0 @@
|
|
1
|
-
import preact from 'preact';
|
2
|
-
import classnames from 'classnames';
|
3
|
-
|
4
|
-
import PrivilegesPanel from './privileges_panel';
|
5
|
-
import RulesPanel from "./rules_panel";
|
6
|
-
|
7
|
-
export default class AppContainer extends preact.Component {
|
8
|
-
|
9
|
-
constructor (props) {
|
10
|
-
super(props);
|
11
|
-
|
12
|
-
this.switchToPrivileges = this.changePanel.bind(this, 'privileges');
|
13
|
-
this.switchToRules = this.changePanel.bind(this, 'rules');
|
14
|
-
}
|
15
|
-
|
16
|
-
render ({app}, {loaded, load_fail, panel}) {
|
17
|
-
if (!loaded) return <div className="splash-message">
|
18
|
-
<code>... loading admission data ...</code>
|
19
|
-
</div>;
|
20
|
-
|
21
|
-
if (load_fail) return <div className="splash-message">
|
22
|
-
<h4>failed to load admission data</h4>
|
23
|
-
<code>{load_fail}</code>
|
24
|
-
</div>;
|
25
|
-
|
26
|
-
return <div className="admission-app-container">
|
27
|
-
<ul className="panels-list">
|
28
|
-
|
29
|
-
<li
|
30
|
-
onClick={this.switchToPrivileges}
|
31
|
-
className={classnames({'active': panel === 'privileges'})}>
|
32
|
-
Privileges Order
|
33
|
-
</li>
|
34
|
-
|
35
|
-
<li
|
36
|
-
onClick={this.switchToRules}
|
37
|
-
className={classnames({'active': panel === 'rules'})}>
|
38
|
-
Rules Listing
|
39
|
-
</li>
|
40
|
-
|
41
|
-
</ul>
|
42
|
-
|
43
|
-
{this.renderPanel()}
|
44
|
-
</div>;
|
45
|
-
}
|
46
|
-
|
47
|
-
componentDidMount () {
|
48
|
-
const store = this.props.app.store;
|
49
|
-
|
50
|
-
this.store_unsibscribe = store.subscribe(() => {
|
51
|
-
const state = store.getState();
|
52
|
-
this.setState({loaded: state.loaded, panel: state.panel});
|
53
|
-
});
|
54
|
-
|
55
|
-
setTimeout(this.props.onMounted, 0);
|
56
|
-
}
|
57
|
-
|
58
|
-
componentWillUnmount () {
|
59
|
-
this.store_unsibscribe();
|
60
|
-
}
|
61
|
-
|
62
|
-
changePanel(panel) {
|
63
|
-
this.props.app.store.dispatch({type: 'PANEL_CHANGE', panel: panel});
|
64
|
-
}
|
65
|
-
|
66
|
-
renderPanel () {
|
67
|
-
const app = this.props.app;
|
68
|
-
switch (this.state.panel) {
|
69
|
-
case 'privileges':
|
70
|
-
return <PrivilegesPanel app={app} />;
|
71
|
-
break;
|
72
|
-
|
73
|
-
case 'rules':
|
74
|
-
return <RulesPanel app={app}/>;
|
75
|
-
break;
|
76
|
-
}
|
77
|
-
}
|
78
|
-
}
|
@@ -1,177 +0,0 @@
|
|
1
|
-
import preact from 'preact';
|
2
|
-
import classnames from 'classnames';
|
3
|
-
import helpers from '../helpers';
|
4
|
-
|
5
|
-
export default class InputWithSelect extends preact.Component {
|
6
|
-
|
7
|
-
constructor (props) {
|
8
|
-
super(props);
|
9
|
-
|
10
|
-
this.state = {
|
11
|
-
text: props.defaultText || '',
|
12
|
-
matching: null,
|
13
|
-
};
|
14
|
-
|
15
|
-
this.setParentRef = ref => this.element = ref;
|
16
|
-
this.setListRef = ref => this.list = ref;
|
17
|
-
this.onKeyDown = this.onKeyDown.bind(this);
|
18
|
-
this.onTextChange = helpers.debounce(this.onTextChange.bind(this), 400);
|
19
|
-
this.toggleList = helpers.debounce(this.toggleList.bind(this), 400, true);
|
20
|
-
this.closeList = this.closeList.bind(this);
|
21
|
-
this.onSelected = this.onSelected.bind(this);
|
22
|
-
}
|
23
|
-
|
24
|
-
render ({placeholder}, {text, matching}) {
|
25
|
-
if (!matching) this.list = null;
|
26
|
-
|
27
|
-
return <div
|
28
|
-
ref={this.setParentRef}
|
29
|
-
className="select_box">
|
30
|
-
|
31
|
-
<div className="_inputs">
|
32
|
-
<input
|
33
|
-
type="text"
|
34
|
-
className="input_text"
|
35
|
-
placeholder={placeholder}
|
36
|
-
onKeyDown={this.onKeyDown}
|
37
|
-
value={text}/>
|
38
|
-
|
39
|
-
<button
|
40
|
-
type="button"
|
41
|
-
tabIndex="-1"
|
42
|
-
className="button"
|
43
|
-
onClick={this.toggleList}>
|
44
|
-
⌄
|
45
|
-
</button>
|
46
|
-
</div>
|
47
|
-
|
48
|
-
{matching && <DropdownList
|
49
|
-
ref={this.setListRef}
|
50
|
-
items={matching}
|
51
|
-
toSelect={this.onSelected}
|
52
|
-
toClose={this.closeList}
|
53
|
-
/>}
|
54
|
-
|
55
|
-
</div>;
|
56
|
-
}
|
57
|
-
|
58
|
-
componentDidMount () {
|
59
|
-
this._outside_click_listener = e => {
|
60
|
-
if (!this.element.contains(e.target) && this.list) {
|
61
|
-
this.closeList();
|
62
|
-
}
|
63
|
-
};
|
64
|
-
document.addEventListener('click', this._outside_click_listener);
|
65
|
-
}
|
66
|
-
|
67
|
-
componentWillUnmount () {
|
68
|
-
document.removeEventListener('click', this._outside_click_listener);
|
69
|
-
}
|
70
|
-
|
71
|
-
componentWillReceiveProps (new_props) {
|
72
|
-
if (new_props.defaultText !== this.props.defaultText) this.setState({text: new_props.defaultText});
|
73
|
-
}
|
74
|
-
|
75
|
-
onKeyDown (e) {
|
76
|
-
if (this.list && this.list.onKeyDown(e)) return;
|
77
|
-
if (e.keyCode === 13 && this.props.enterable) {
|
78
|
-
this.onSelected(e.target.value.trim());
|
79
|
-
return;
|
80
|
-
}
|
81
|
-
this.onTextChange(e);
|
82
|
-
}
|
83
|
-
|
84
|
-
onTextChange (e) {
|
85
|
-
const text = e.target.value.trim();
|
86
|
-
let matching = null;
|
87
|
-
if ((text && text !==this.state.text) || e.keyCode === 40) {
|
88
|
-
matching = this.filtered_items(text);
|
89
|
-
}
|
90
|
-
this.setState({matching, text});
|
91
|
-
}
|
92
|
-
|
93
|
-
toggleList () {
|
94
|
-
if (this.list) {
|
95
|
-
this.closeList();
|
96
|
-
|
97
|
-
} else {
|
98
|
-
this.setState({matching: this.filtered_items()});
|
99
|
-
}
|
100
|
-
}
|
101
|
-
|
102
|
-
closeList () {
|
103
|
-
this.setState({matching: null});
|
104
|
-
}
|
105
|
-
|
106
|
-
onSelected (value) {
|
107
|
-
this.setState({text: value, matching: null});
|
108
|
-
this.props.onSelect(value);
|
109
|
-
}
|
110
|
-
|
111
|
-
filtered_items (text) {
|
112
|
-
let items = this.props.all_items;
|
113
|
-
if (text) items = items.filter(value => value.startsWith(text));
|
114
|
-
if (this.props.nullable) items.unshift(null);
|
115
|
-
|
116
|
-
return items.length === 0 ? null : items;
|
117
|
-
}
|
118
|
-
|
119
|
-
}
|
120
|
-
|
121
|
-
class DropdownList extends preact.Component {
|
122
|
-
|
123
|
-
constructor (props) {
|
124
|
-
super(props);
|
125
|
-
this.state = {selected: -1};
|
126
|
-
}
|
127
|
-
|
128
|
-
render ({items, toSelect}, {selected}) {
|
129
|
-
return <div
|
130
|
-
className="_dropdown">
|
131
|
-
<ul>
|
132
|
-
{items.map((name, i) => <li
|
133
|
-
className={classnames({'selected': selected === i})}
|
134
|
-
onClick={() => toSelect(name)}>
|
135
|
-
{name === null ? '\u00A0' : name}
|
136
|
-
</li>)}
|
137
|
-
</ul>
|
138
|
-
</div>;
|
139
|
-
}
|
140
|
-
|
141
|
-
onKeyDown (e) {
|
142
|
-
switch (e.keyCode) {
|
143
|
-
case 40: // down
|
144
|
-
this.changeSelection(1);
|
145
|
-
return true;
|
146
|
-
break;
|
147
|
-
|
148
|
-
case 38: // up
|
149
|
-
this.changeSelection(-1);
|
150
|
-
return true;
|
151
|
-
break;
|
152
|
-
|
153
|
-
case 13: // enter
|
154
|
-
const selected = this.state.selected;
|
155
|
-
if (selected !== -1) {
|
156
|
-
this.props.toSelect(this.props.items[selected]);
|
157
|
-
return true;
|
158
|
-
}
|
159
|
-
break;
|
160
|
-
|
161
|
-
case 27: // escape
|
162
|
-
this.props.toClose();
|
163
|
-
return true;
|
164
|
-
break;
|
165
|
-
|
166
|
-
}
|
167
|
-
return false;
|
168
|
-
}
|
169
|
-
|
170
|
-
changeSelection (value) {
|
171
|
-
let selected = this.state.selected;
|
172
|
-
selected += value;
|
173
|
-
if (selected < 0) selected = 0;
|
174
|
-
if (selected >= this.props.items.length) selected = this.props.items.length -1;
|
175
|
-
this.setState({selected});
|
176
|
-
}
|
177
|
-
}
|
@@ -1,48 +0,0 @@
|
|
1
|
-
import preact from 'preact';
|
2
|
-
|
3
|
-
export default class NestedListRow extends preact.Component {
|
4
|
-
|
5
|
-
constructor (props) {
|
6
|
-
super(props);
|
7
|
-
|
8
|
-
this.state = {
|
9
|
-
unrolled: !!props.defaultUnrolled
|
10
|
-
};
|
11
|
-
|
12
|
-
this.toggleRollOut = this.toggleRollOut.bind(this);
|
13
|
-
}
|
14
|
-
|
15
|
-
render ({app, content, nestedRows}, {unrolled}) {
|
16
|
-
return <li>
|
17
|
-
<div onClick={this.toggleRollOut} className="nested-list-content">
|
18
|
-
{nestedRows && <span className="icon">
|
19
|
-
{unrolled ? '\u25B6' : '\u25BC'}
|
20
|
-
</span>
|
21
|
-
}
|
22
|
-
<span className="content">{content}</span>
|
23
|
-
</div>
|
24
|
-
{unrolled && nestedRows && <ul className="nested-list">{
|
25
|
-
nestedRows.map(row =>
|
26
|
-
<NestedListRow
|
27
|
-
app={app}
|
28
|
-
content={row.content}
|
29
|
-
nestedRows={row.nested_rows}
|
30
|
-
defaultUnrolled={this.props.defaultUnrolled}
|
31
|
-
/>
|
32
|
-
)
|
33
|
-
}</ul>}
|
34
|
-
</li>;
|
35
|
-
}
|
36
|
-
|
37
|
-
componentWillReceiveProps ({defaultUnrolled}) {
|
38
|
-
if (defaultUnrolled !== this.props.defaultUnrolled) {
|
39
|
-
this.setState({unrolled: !!defaultUnrolled});
|
40
|
-
}
|
41
|
-
}
|
42
|
-
|
43
|
-
toggleRollOut () {
|
44
|
-
this.setState({unrolled: !this.state.unrolled});
|
45
|
-
}
|
46
|
-
|
47
|
-
}
|
48
|
-
|