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.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.idea/.rakeTasks +7 -0
  4. data/.idea/inspectionProfiles/Project_Default.xml +16 -0
  5. data/.idea/misc.xml +7 -0
  6. data/.idea/modules.xml +8 -0
  7. data/.idea/surikat.iml +50 -0
  8. data/.idea/vcs.xml +6 -0
  9. data/.idea/workspace.xml +744 -0
  10. data/.rspec +2 -0
  11. data/.travis.yml +5 -0
  12. data/Gemfile +6 -0
  13. data/LICENSE.txt +21 -0
  14. data/README.md +399 -0
  15. data/Rakefile +6 -0
  16. data/bin/console +11 -0
  17. data/bin/setup +8 -0
  18. data/exe/surikat +234 -0
  19. data/lib/surikat.rb +421 -0
  20. data/lib/surikat/base_model.rb +35 -0
  21. data/lib/surikat/base_queries.rb +10 -0
  22. data/lib/surikat/base_type.rb +3 -0
  23. data/lib/surikat/configurations.rb +22 -0
  24. data/lib/surikat/new_app.rb +108 -0
  25. data/lib/surikat/routes.rb +67 -0
  26. data/lib/surikat/scaffold.rb +503 -0
  27. data/lib/surikat/session.rb +35 -0
  28. data/lib/surikat/session_manager.rb +92 -0
  29. data/lib/surikat/templates/.rspec.tmpl +1 -0
  30. data/lib/surikat/templates/.standalone_migrations.tmpl +6 -0
  31. data/lib/surikat/templates/Gemfile.tmpl +31 -0
  32. data/lib/surikat/templates/Rakefile.tmpl +2 -0
  33. data/lib/surikat/templates/aaa_queries.rb.tmpl +124 -0
  34. data/lib/surikat/templates/aaa_spec.rb.tmpl +151 -0
  35. data/lib/surikat/templates/application.yml.tmpl +14 -0
  36. data/lib/surikat/templates/base_aaa_model.rb.tmpl +28 -0
  37. data/lib/surikat/templates/base_model.rb.tmpl +6 -0
  38. data/lib/surikat/templates/base_spec.rb.tmpl +148 -0
  39. data/lib/surikat/templates/config.ru.tmpl +61 -0
  40. data/lib/surikat/templates/console.tmpl +14 -0
  41. data/lib/surikat/templates/crud_queries.rb.tmpl +105 -0
  42. data/lib/surikat/templates/database.yml.tmpl +26 -0
  43. data/lib/surikat/templates/hello_queries.rb.tmpl +19 -0
  44. data/lib/surikat/templates/hello_spec.rb.tmpl +39 -0
  45. data/lib/surikat/templates/routes.yml.tmpl +15 -0
  46. data/lib/surikat/templates/spec_helper.rb.tmpl +11 -0
  47. data/lib/surikat/templates/test_helper.rb.tmpl +30 -0
  48. data/lib/surikat/types.rb +45 -0
  49. data/lib/surikat/version.rb +3 -0
  50. data/lib/surikat/yaml_configurator.rb +18 -0
  51. data/surikat.gemspec +47 -0
  52. 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,6 @@
1
+ db:
2
+ seeds: db/seeds.rb
3
+ migrate: db/migrate
4
+ schema: db/schema.rb
5
+ config:
6
+ database: config/database.yml
@@ -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,2 @@
1
+ require 'standalone_migrations'
2
+ StandaloneMigrations::Tasks.load_tasks
@@ -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