surikat 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|