surikat 0.2.2
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 +7 -0
- data/.gitignore +15 -0
- data/.idea/.rakeTasks +7 -0
- data/.idea/inspectionProfiles/Project_Default.xml +16 -0
- data/.idea/misc.xml +7 -0
- data/.idea/modules.xml +8 -0
- data/.idea/surikat.iml +50 -0
- data/.idea/vcs.xml +6 -0
- data/.idea/workspace.xml +744 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +399 -0
- data/Rakefile +6 -0
- data/bin/console +11 -0
- data/bin/setup +8 -0
- data/exe/surikat +234 -0
- data/lib/surikat.rb +421 -0
- data/lib/surikat/base_model.rb +35 -0
- data/lib/surikat/base_queries.rb +10 -0
- data/lib/surikat/base_type.rb +3 -0
- data/lib/surikat/configurations.rb +22 -0
- data/lib/surikat/new_app.rb +108 -0
- data/lib/surikat/routes.rb +67 -0
- data/lib/surikat/scaffold.rb +503 -0
- data/lib/surikat/session.rb +35 -0
- data/lib/surikat/session_manager.rb +92 -0
- data/lib/surikat/templates/.rspec.tmpl +1 -0
- data/lib/surikat/templates/.standalone_migrations.tmpl +6 -0
- data/lib/surikat/templates/Gemfile.tmpl +31 -0
- data/lib/surikat/templates/Rakefile.tmpl +2 -0
- data/lib/surikat/templates/aaa_queries.rb.tmpl +124 -0
- data/lib/surikat/templates/aaa_spec.rb.tmpl +151 -0
- data/lib/surikat/templates/application.yml.tmpl +14 -0
- data/lib/surikat/templates/base_aaa_model.rb.tmpl +28 -0
- data/lib/surikat/templates/base_model.rb.tmpl +6 -0
- data/lib/surikat/templates/base_spec.rb.tmpl +148 -0
- data/lib/surikat/templates/config.ru.tmpl +61 -0
- data/lib/surikat/templates/console.tmpl +14 -0
- data/lib/surikat/templates/crud_queries.rb.tmpl +105 -0
- data/lib/surikat/templates/database.yml.tmpl +26 -0
- data/lib/surikat/templates/hello_queries.rb.tmpl +19 -0
- data/lib/surikat/templates/hello_spec.rb.tmpl +39 -0
- data/lib/surikat/templates/routes.yml.tmpl +15 -0
- data/lib/surikat/templates/spec_helper.rb.tmpl +11 -0
- data/lib/surikat/templates/test_helper.rb.tmpl +30 -0
- data/lib/surikat/types.rb +45 -0
- data/lib/surikat/version.rb +3 -0
- data/lib/surikat/yaml_configurator.rb +18 -0
- data/surikat.gemspec +47 -0
- metadata +199 -0
@@ -0,0 +1,35 @@
|
|
1
|
+
module Surikat
|
2
|
+
|
3
|
+
require 'surikat/session_manager'
|
4
|
+
|
5
|
+
class Session
|
6
|
+
|
7
|
+
def initialize(session_key)
|
8
|
+
@manager = Surikat::SessionManager.new
|
9
|
+
@session_key = session_key
|
10
|
+
@this_session = @manager[session_key] || {}
|
11
|
+
|
12
|
+
if @this_session.blank? && !@session_key.blank?
|
13
|
+
@manager.merge! @session_key, {created_at: Time.now}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def [](key)
|
18
|
+
@this_session[key]
|
19
|
+
end
|
20
|
+
|
21
|
+
def []=(key, value)
|
22
|
+
@manager.merge! @session_key, {key => value}
|
23
|
+
end
|
24
|
+
|
25
|
+
def delete(key)
|
26
|
+
@manager.delete_key! @session_key, key
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_h
|
30
|
+
@this_session
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module Surikat
|
2
|
+
class SessionManager
|
3
|
+
def initialize
|
4
|
+
@config = Surikat.config.app['session']
|
5
|
+
|
6
|
+
#puts "Session manager configured with #{@config.inspect}"
|
7
|
+
|
8
|
+
case @config['storage']
|
9
|
+
when 'redis'
|
10
|
+
@store = Redis.new url: @config['redis_url']
|
11
|
+
when 'file'
|
12
|
+
@filename = @config['file'] || 'surikat_session_store'
|
13
|
+
@store = Marshal.load(File.read(@filename)) rescue {}
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :config
|
19
|
+
|
20
|
+
def [](key)
|
21
|
+
case @config['storage']
|
22
|
+
when 'file'
|
23
|
+
@store[key]
|
24
|
+
when 'redis'
|
25
|
+
existing = @store.get("surikat_session_key_#{key}")
|
26
|
+
existing ? Marshal.load(existing) : {}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# def create
|
31
|
+
# key = SecureRandom.hex(30)
|
32
|
+
# merge! key, {created_at: Time.now}
|
33
|
+
# key
|
34
|
+
# end
|
35
|
+
|
36
|
+
def merge!(key, hash)
|
37
|
+
return if key.nil?
|
38
|
+
|
39
|
+
case @config['storage']
|
40
|
+
|
41
|
+
when 'redis'
|
42
|
+
redis_key = "surikat_session_key_#{key}"
|
43
|
+
if existing = @store.get(redis_key)
|
44
|
+
existing_object = Marshal.load(existing)
|
45
|
+
new_object = existing_object.merge(hash)
|
46
|
+
else
|
47
|
+
new_object = hash
|
48
|
+
end
|
49
|
+
new_data = Marshal.dump(new_object)
|
50
|
+
@store.set(redis_key, new_data)
|
51
|
+
|
52
|
+
when 'file'
|
53
|
+
if @store[key]
|
54
|
+
@store[key].merge!(hash)
|
55
|
+
else
|
56
|
+
@store[key] = hash
|
57
|
+
end
|
58
|
+
File.open(@filename, 'w') {|f| f.write Marshal.dump(@store)}
|
59
|
+
end
|
60
|
+
|
61
|
+
true
|
62
|
+
end
|
63
|
+
|
64
|
+
def destroy!(skey)
|
65
|
+
case @config['storage']
|
66
|
+
when 'redis'
|
67
|
+
@store.del("surikat_session_key_#{skey}")
|
68
|
+
when 'file'
|
69
|
+
@store.delete(skey)
|
70
|
+
File.open(@filename, 'w') {|f| f.write Marshal.dump(@store)}
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def delete_key!(skey, key)
|
75
|
+
case @config['storage']
|
76
|
+
when 'redis'
|
77
|
+
redis_key = "surikat_session_key_#{key}"
|
78
|
+
if existing = @store.get(redis_key)
|
79
|
+
existing_object = Marshal.load(existing)
|
80
|
+
new_object = existing_object.delete(key)
|
81
|
+
|
82
|
+
new_data = Marshal.dump(new_object)
|
83
|
+
end
|
84
|
+
@store.set(redis_key, new_data)
|
85
|
+
when 'file'
|
86
|
+
@store[skey].delete(key)
|
87
|
+
File.open(@filename, 'w') {|f| f.write Marshal.dump(@store)}
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
--require spec_helper
|
@@ -0,0 +1,31 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# This is a rack app
|
4
|
+
gem 'rack-app'
|
5
|
+
|
6
|
+
# ActiveRecord is used as a database layer
|
7
|
+
gem 'activerecord'
|
8
|
+
|
9
|
+
# ActiveSupport for various duties
|
10
|
+
gem 'activesupport'
|
11
|
+
|
12
|
+
# Surikat is the engine of the app
|
13
|
+
gem 'surikat'
|
14
|
+
|
15
|
+
# The web server
|
16
|
+
gem 'passenger', ">= 5.0.25", require: "phusion_passenger/rack_handler"
|
17
|
+
|
18
|
+
# The GraphQL parser
|
19
|
+
gem 'graphql-libgraphqlparser', ">= 1.2.0"
|
20
|
+
|
21
|
+
# A faster JSON parser
|
22
|
+
gem 'oj'
|
23
|
+
|
24
|
+
# The initial database is SQLite
|
25
|
+
gem 'sqlite3'
|
26
|
+
|
27
|
+
# Database migrations for both schema and data
|
28
|
+
gem 'standalone_migrations'
|
29
|
+
|
30
|
+
# Filtering
|
31
|
+
gem 'ransack', ">= 1.8.8"
|
@@ -0,0 +1,124 @@
|
|
1
|
+
=begin
|
2
|
+
This class was generated by the scaffold generator.
|
3
|
+
It contains methods to handle authentication, authorization and access, using the User model.
|
4
|
+
Default routes are created for each of this method (use 'surikat list routes' or look inside config/routes.yml to see them).
|
5
|
+
Example queries/mutations can be found in the comments for each method.
|
6
|
+
Generated at:: %{time}
|
7
|
+
|
8
|
+
To test these queries, run 'rspec -f d spec/aaa_spec.rb'
|
9
|
+
=end
|
10
|
+
|
11
|
+
class AAAQueries < Surikat::BaseQueries
|
12
|
+
=begin
|
13
|
+
Description: Authenticate a user. On successful authentication, Surikat will save the +id+ of the user in the
|
14
|
+
session, and then return the session key, which the frontend client must then carry to the next request.
|
15
|
+
If the authentication was not successful, a nil value is returned.
|
16
|
+
Query Name: Authenticate
|
17
|
+
|
18
|
+
Input: { 'email' => String, 'password' => String }
|
19
|
+
|
20
|
+
OutputType: Boolean
|
21
|
+
|
22
|
+
Query Example:
|
23
|
+
{ Authenticate(email: 'a@b.c', password: 'abc') }
|
24
|
+
=end
|
25
|
+
def authenticate
|
26
|
+
user = User.authenticate(arguments)
|
27
|
+
return nil unless user
|
28
|
+
|
29
|
+
session[:user_id] = user.id
|
30
|
+
true
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
=begin
|
35
|
+
Description: Log a user out. The user's session is destroyed.
|
36
|
+
Query Name: Logout
|
37
|
+
|
38
|
+
OutputType: Boolean (always true)
|
39
|
+
|
40
|
+
Query Example:
|
41
|
+
{ Logout }
|
42
|
+
=end
|
43
|
+
def logout
|
44
|
+
session.delete :user_id
|
45
|
+
true
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
=begin
|
50
|
+
Description: Returns the current user. Normally there's little reason to call this; the
|
51
|
+
assumption is that the frontend remembers who the current user is.
|
52
|
+
Query Name: CurrentUser
|
53
|
+
|
54
|
+
OutputType: User
|
55
|
+
|
56
|
+
Query Example:
|
57
|
+
{ CurrentUser {
|
58
|
+
id
|
59
|
+
email
|
60
|
+
}
|
61
|
+
}
|
62
|
+
=end
|
63
|
+
def current_user
|
64
|
+
User.where(id: session[:user_id]).first
|
65
|
+
end
|
66
|
+
|
67
|
+
=begin
|
68
|
+
Description: Login as another user. The route for this query should have a +permitted_roles+ value of ['superadmin']
|
69
|
+
or something similar, so that only superadmins may login as somebody else. The +id+ of the current user is preserved
|
70
|
+
in the session inside +superadmin_id+ and is used by another query, +BackFromLoginAs+.
|
71
|
+
Query Name: LoginAs
|
72
|
+
|
73
|
+
OutputType: Boolean
|
74
|
+
|
75
|
+
Query Example:
|
76
|
+
{ LoginAs(user_id: 2) }
|
77
|
+
=end
|
78
|
+
def login_as
|
79
|
+
new_user = User.where(id: arguments['user_id']).first
|
80
|
+
if new_user
|
81
|
+
current_user_id = session[:user_id]
|
82
|
+
session[:user_id] = new_user.id
|
83
|
+
session[:logged_in_at] = Time.now
|
84
|
+
session[:superadmin_id] = current_user_id
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
=begin
|
89
|
+
Description: After having logged in as someone else, the superadmin can become again his own self.
|
90
|
+
Query Name: BackFromLoginAs
|
91
|
+
|
92
|
+
OutputType: Boolean
|
93
|
+
|
94
|
+
Query Example:
|
95
|
+
{ BackFromLoginAs }
|
96
|
+
=end
|
97
|
+
def back_from_login_as
|
98
|
+
superadmin = User.where(id: session[:superadmin_id]).first
|
99
|
+
if superadmin
|
100
|
+
session[:user_id] = superadmin.id
|
101
|
+
session[:superadmin_id] = nil
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
=begin
|
107
|
+
Just some demo queries used by the rspec tests. If you delete them, make sure to also delete the relevant tests in +spec/aaa_spec.rb+.
|
108
|
+
=end
|
109
|
+
def demo_one
|
110
|
+
u = User.where(id: session['user_id']).first
|
111
|
+
"if you see this, you are logged in as #{u&.email} since #{session[:logged_in_at]}."
|
112
|
+
end
|
113
|
+
|
114
|
+
def demo_two
|
115
|
+
u = User.where(id: session['user_id']).first
|
116
|
+
"if you see this, you are logged in as #{u&.email} since #{session[:logged_in_at]} (and you have an acceptable user role)."
|
117
|
+
end
|
118
|
+
|
119
|
+
def demo_three
|
120
|
+
u = User.where(id: session['user_id']).first
|
121
|
+
"if you see this, you are logged in as #{u&.email} since #{session[:logged_in_at]} (and you have an acceptable user role)."
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe AAAQueries do
|
4
|
+
|
5
|
+
before :all do
|
6
|
+
@user = User.create email:'a@b.com', password: 'onetwothree', roleids: 'worker'
|
7
|
+
@superadmin = User.create email:'super@a.com', password: 'onetwothree', roleids: 'superadmin'
|
8
|
+
end
|
9
|
+
|
10
|
+
before :each do
|
11
|
+
@session_key = SecureRandom.hex(5)
|
12
|
+
end
|
13
|
+
|
14
|
+
context "When testing Authentication, Authorization and Access" do
|
15
|
+
describe "#authentication" do
|
16
|
+
|
17
|
+
it "should correctly authenticate User" do
|
18
|
+
query = "{ Authenticate(email: \"a@b.com\", password: \"onetwothree\") }"
|
19
|
+
response = Surikat::run query
|
20
|
+
|
21
|
+
expect(response['Authenticate']).to be true
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should incorrectly authenticate User" do
|
25
|
+
query = "{ Authenticate(email: \"a@b.com\", password: \"definitely not onetwothree\") }"
|
26
|
+
response = Surikat::run query
|
27
|
+
|
28
|
+
expect(response['Authenticate']).to eq :error => 'No result'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "#authorization" do
|
33
|
+
|
34
|
+
it "should allow a logged in user to access a private route" do
|
35
|
+
# First, log a user in
|
36
|
+
query = "{ Authenticate(email: \"a@b.com\", password: \"onetwothree\") }"
|
37
|
+
Surikat::run query, {}, session_key: @session_key
|
38
|
+
|
39
|
+
# Then, make sure they have access to a private query
|
40
|
+
query = "{ DemoOne }"
|
41
|
+
response = Surikat::run query, {}, session_key: @session_key
|
42
|
+
|
43
|
+
expect(response['DemoOne']).to include "if you see this"
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should not allow a non-logged in user to access a private route" do
|
47
|
+
query = "{ DemoOne }"
|
48
|
+
response = Surikat::run query
|
49
|
+
expect(response['DemoOne']).to_not include "if you see this"
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
it "should log out a user" do
|
54
|
+
# First, log a user in
|
55
|
+
query = "{ Authenticate(email: \"a@b.com\", password: \"onetwothree\") }"
|
56
|
+
Surikat::run query, {}, session_key: @session_key
|
57
|
+
|
58
|
+
# Then, log them out
|
59
|
+
query = "{ Logout }"
|
60
|
+
response = Surikat::run query, {}, {session_key: @session_key}
|
61
|
+
expect(response['Logout']).to be true
|
62
|
+
|
63
|
+
# Then, check that they don't have access to a private query
|
64
|
+
query = "{ DemoOne }"
|
65
|
+
response = Surikat::run query, {}, {session_key: @session_key}
|
66
|
+
|
67
|
+
expect(response['DemoOne']).not_to include "if you see this"
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should retrieve the current user" do
|
71
|
+
# First, log a user in
|
72
|
+
query = "{ Authenticate(email: \"a@b.com\", password: \"onetwothree\") }"
|
73
|
+
Surikat::run query, {}, {session_key: @session_key}
|
74
|
+
|
75
|
+
query = "{ CurrentUser {id} }"
|
76
|
+
response = Surikat::run query, {}, {session_key: @session_key}
|
77
|
+
|
78
|
+
expect(response['CurrentUser']).to eq 'id' => @user.id
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe "#access" do
|
83
|
+
it "should not allow a user of the wrong role to a route protected by roles" do
|
84
|
+
# First, log a user in
|
85
|
+
query = "{ Authenticate(email: \"a@b.com\", password: \"onetwothree\") }"
|
86
|
+
Surikat::run query, {}, session_key: @session_key
|
87
|
+
|
88
|
+
# Then, make sure they do not have access to a private query
|
89
|
+
query = "{ DemoTwo }"
|
90
|
+
response = Surikat::run query, {}, {session_key: @session_key}
|
91
|
+
|
92
|
+
expect(response['DemoTwo']).to_not include 'if you see this'
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should allow a user of the right role to a route protected by roles" do
|
96
|
+
# First, log a user in
|
97
|
+
query = "{ Authenticate(email: \"a@b.com\", password: \"onetwothree\") }"
|
98
|
+
Surikat::run query, {}, session_key: @session_key
|
99
|
+
|
100
|
+
# Then, make sure they have access to a private query
|
101
|
+
query = "{ DemoThree }"
|
102
|
+
response = Surikat::run query, {}, {session_key: @session_key}
|
103
|
+
|
104
|
+
expect(response['DemoThree']).to include "if you see this"
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should allow a superadmin to login as another user" do
|
108
|
+
# First, log a superadmin in
|
109
|
+
query = "{ Authenticate(email: \"#{@superadmin.email}\", password: \"onetwothree\") }"
|
110
|
+
response = Surikat::run query, {}, session_key: @session_key
|
111
|
+
session_key = response['Authenticate']
|
112
|
+
|
113
|
+
# Then, create another user and login as them
|
114
|
+
user = User.create_random
|
115
|
+
|
116
|
+
query = "{ LoginAs(user_id: #{user.id}) }"
|
117
|
+
Surikat::run query, {}, {session_key: @session_key}
|
118
|
+
|
119
|
+
# To test, use the CurrentUser query
|
120
|
+
query = "{ CurrentUser {id} }"
|
121
|
+
response = Surikat::run query, {}, {session_key: @session_key}
|
122
|
+
|
123
|
+
expect(response['CurrentUser']).to include 'id' => user.id
|
124
|
+
end
|
125
|
+
|
126
|
+
it "should allow a superadmin to return after having logged in as someone else" do
|
127
|
+
# First, log a superadmin in
|
128
|
+
query = "{ Authenticate(email: \"#{@superadmin.email}\", password: \"onetwothree\") }"
|
129
|
+
Surikat::run query, {}, session_key: @session_key
|
130
|
+
|
131
|
+
# Then, create another user and login as them
|
132
|
+
user = User.create_random
|
133
|
+
|
134
|
+
query = "{ LoginAs(user_id: #{user.id}) }"
|
135
|
+
Surikat::run query, {}, {session_key: @session_key}
|
136
|
+
|
137
|
+
# Then, go back as the superadmin
|
138
|
+
query = " { BackFromLoginAs }"
|
139
|
+
Surikat::run query, {}, {session_key: @session_key}
|
140
|
+
|
141
|
+
# To test, use the CurrentUser query
|
142
|
+
query = "{ CurrentUser {id} }"
|
143
|
+
response = Surikat::run query, {}, {session_key: @session_key}
|
144
|
+
|
145
|
+
expect(response['CurrentUser']).to include 'id' => @superadmin.id
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|