surikat 0.2.2

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