tiun 0.0.1 → 0.0.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 +4 -4
- data/Gemfile +1 -0
- data/README.md +2 -2
- data/lib/config/locale/ru.yml +8 -0
- data/lib/tiun/auth/controller.rb +228 -0
- data/lib/tiun/auth.rb +85 -0
- data/lib/tiun/autocontroller.rb.erb +4 -2
- data/lib/tiun/automodel.rb.erb +4 -1
- data/lib/tiun/base.rb +110 -76
- data/lib/tiun/core_helper.rb +8 -0
- data/lib/tiun/engine.rb +1 -0
- data/lib/tiun/model/account.rb +30 -0
- data/lib/tiun/model/auth.rb +88 -0
- data/lib/tiun/model/token.rb +13 -0
- data/lib/tiun/policy.rb +2 -22
- data/lib/tiun/version.rb +1 -1
- data/lib/tiun.rb +41 -14
- data/tiun.gemspec +1 -1
- metadata +9 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 21e5c97ff2ba65ab332a7fd9e268dba1b75f9a3bc5047158d2f13e83645fc493
|
4
|
+
data.tar.gz: bbbd5267a33c1191ff2196748e0c95f0f9847713b76dcf7555e02ab3f497accb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0ec044da0a8a1821a9732eb305b864626c301f1fe8c87eea1a6e7e8af55c138acee0cffee09690545241c90938cdc430e0c74a5baeb817c0db0ebf7ab84a1dfd
|
7
|
+
data.tar.gz: 64060e2051a3b0d280719ce69f3f006400686753a02e28f093e510b91403690486b6e875dfc384628e234ced08549b0b096d36f241c68423510044448b06535e
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -32,7 +32,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
32
32
|
|
33
33
|
## Contributing
|
34
34
|
|
35
|
-
Bug reports and pull requests are welcome on GitHub at https://
|
35
|
+
Bug reports and pull requests are welcome on GitHub at https://codeberg.org/znamenica/tiun. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
36
36
|
|
37
37
|
## License
|
38
38
|
|
@@ -40,4 +40,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
40
40
|
|
41
41
|
## Code of Conduct
|
42
42
|
|
43
|
-
Everyone interacting in the Tiun project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://
|
43
|
+
Everyone interacting in the Tiun project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://codeberg.org/znamenica/tiun/blob/master/CODE_OF_CONDUCT.md).
|
@@ -0,0 +1,228 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'tiun/auth'
|
3
|
+
|
4
|
+
module Tiun::Auth::Controller
|
5
|
+
class NoTokenError < StandardError; end
|
6
|
+
class InvalidTokenError < StandardError; end
|
7
|
+
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
included do
|
11
|
+
include Tiun::Auth
|
12
|
+
|
13
|
+
before_action :set_languages
|
14
|
+
before_action :authenticate
|
15
|
+
before_action :fetch_token, only: %i(update show destroy)
|
16
|
+
before_action :authorize!, only: %i(create update show destroy)
|
17
|
+
after_action :obsolete_token, only: %i(update show destroy)
|
18
|
+
after_action :session_update, only: %i(create update show destroy)
|
19
|
+
after_action :drop_auth, only: %i(destroy)
|
20
|
+
|
21
|
+
rescue_from Exception, with: :exception
|
22
|
+
rescue_from ActiveRecord::RecordNotFound, with: :unauthenticated
|
23
|
+
rescue_from Tiun::Model::Account::InvalidPasswordError, with: :unauthenticated
|
24
|
+
rescue_from NoTokenError, InvalidTokenError, with: :invalid_token
|
25
|
+
end
|
26
|
+
|
27
|
+
# POST /session.json
|
28
|
+
def create
|
29
|
+
@session = @current_user.generate_session
|
30
|
+
|
31
|
+
respond_to do |format|
|
32
|
+
format.html { redirect_to :root, notice: I18n.t("tiun.session.created") }
|
33
|
+
format.json { render json: session_data }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# PUT /session.json
|
38
|
+
def update
|
39
|
+
@session = @current_user.update_session(@token)
|
40
|
+
|
41
|
+
respond_to do |format|
|
42
|
+
format.html { redirect_to :root, notice: I18n.t("tiun.session.updated") }
|
43
|
+
format.json { render json: session_data }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# GET /session.json
|
48
|
+
def show
|
49
|
+
@session = @current_user.current_session(@token)
|
50
|
+
|
51
|
+
respond_to do |format|
|
52
|
+
format.html { redirect_to :root }
|
53
|
+
format.json { render json: session_data }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# DELETE /session.json
|
58
|
+
def destroy
|
59
|
+
@session = @current_user.destroy_session(@token)
|
60
|
+
|
61
|
+
respond_to do |format|
|
62
|
+
format.html { redirect_to :root, notice: I18n.t("tiun.session.deleted") }
|
63
|
+
format.json { render json: serialize_collection([]) }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
protected
|
68
|
+
|
69
|
+
def drop_auth
|
70
|
+
session.delete("user")
|
71
|
+
end
|
72
|
+
|
73
|
+
def model_name
|
74
|
+
@_model_name ||= 'Token'
|
75
|
+
end
|
76
|
+
|
77
|
+
def param_name
|
78
|
+
@_param_name ||= model_name.tableize.singularize
|
79
|
+
end
|
80
|
+
|
81
|
+
def user_model
|
82
|
+
@_user_model ||= User
|
83
|
+
end
|
84
|
+
|
85
|
+
def account_model_name
|
86
|
+
@account_model_name ||= "Account"
|
87
|
+
end
|
88
|
+
|
89
|
+
def account_model
|
90
|
+
@account_model ||= account_model_name.constantize
|
91
|
+
end
|
92
|
+
|
93
|
+
def unauthenticated e
|
94
|
+
render_code 401
|
95
|
+
end
|
96
|
+
|
97
|
+
def render_code code
|
98
|
+
args = params.permit(*permitted_filter).to_h
|
99
|
+
res = { status: code, json: { args: args }}
|
100
|
+
|
101
|
+
args.present? ? render(**res) : head(code)
|
102
|
+
end
|
103
|
+
|
104
|
+
def invalid_token e
|
105
|
+
render_code 404
|
106
|
+
end
|
107
|
+
|
108
|
+
def exception e
|
109
|
+
error = "[#{e.class}]{exception}> #{e.message} \n\t #{e.backtrace.join("\n\t")}"
|
110
|
+
args = params.permit(*permitted_filter).to_h
|
111
|
+
render json: { args: args, error: error }, status: 500
|
112
|
+
end
|
113
|
+
|
114
|
+
def locales
|
115
|
+
I18n.available_locales
|
116
|
+
end
|
117
|
+
|
118
|
+
def supplement_names
|
119
|
+
[:created_at, :updated_at]
|
120
|
+
end
|
121
|
+
|
122
|
+
def context
|
123
|
+
@_context ||= {
|
124
|
+
except: supplement_names,
|
125
|
+
locales: locales,
|
126
|
+
languages: @languages,
|
127
|
+
}
|
128
|
+
end
|
129
|
+
|
130
|
+
def set_languages
|
131
|
+
# NOTE the default values
|
132
|
+
@languages ||= %i(ру)
|
133
|
+
end
|
134
|
+
|
135
|
+
def default_arg
|
136
|
+
self.class.instance_variable_get(:@default_arg)&.[](action_name) || "id"
|
137
|
+
rescue NameError
|
138
|
+
"id"
|
139
|
+
end
|
140
|
+
|
141
|
+
def fetch_token
|
142
|
+
if params[:type] && params[:token]
|
143
|
+
raise NoTokenError unless token = params[:token]
|
144
|
+
raise InvalidTokenError.new(auth) unless type = params[:type]
|
145
|
+
else
|
146
|
+
raise NoTokenError unless auth = request.headers["Authorization"]
|
147
|
+
raise InvalidTokenError.new(auth) unless /(?<type>[\w]+) (?<token>.*)/ =~ auth
|
148
|
+
raise InvalidTokenError.new(auth) unless require_token_table.include?(type)
|
149
|
+
end
|
150
|
+
|
151
|
+
attrs = { code: token, type: "Token::#{type[0..7].camelcase}", obsoleted_at: nil }
|
152
|
+
@token = Token.where(attrs).first || raise(InvalidTokenError)
|
153
|
+
end
|
154
|
+
|
155
|
+
def require_token_table
|
156
|
+
{
|
157
|
+
"update" => ["Validate", "Refresh"],
|
158
|
+
"show" => ["Session"],
|
159
|
+
"destroy" => ["Session", "Refresh"],
|
160
|
+
}[action_name] || []
|
161
|
+
end
|
162
|
+
|
163
|
+
def parms
|
164
|
+
@parms ||= params[account_model_name.downcase] || params
|
165
|
+
end
|
166
|
+
|
167
|
+
def account
|
168
|
+
arg_name = default_arg
|
169
|
+
|
170
|
+
@account ||=
|
171
|
+
if !arg_name
|
172
|
+
account_model.find(params[account_model.primary_key])
|
173
|
+
elsif account_model.respond_to?("by_credentials_or_id")
|
174
|
+
account_model.by_credentials_or_id(parms[arg_name]).first
|
175
|
+
else
|
176
|
+
account_model.where({ arg_name => parms[arg_name] }).first
|
177
|
+
end# || raise(ActiveRecord::RecordNotFound)
|
178
|
+
end
|
179
|
+
|
180
|
+
def authenticate
|
181
|
+
@current_user ||=
|
182
|
+
if session["user"]
|
183
|
+
User.find_by_id(session["user"]["id"])
|
184
|
+
else
|
185
|
+
account&.authenticated_user!(parms[:password])
|
186
|
+
end
|
187
|
+
rescue
|
188
|
+
end
|
189
|
+
|
190
|
+
def authorize!
|
191
|
+
@current_user ||= @token.tokenable.is_a?(user_model) ? @token.tokenable : @token.tokenable.user
|
192
|
+
end
|
193
|
+
|
194
|
+
def _permitted_filter
|
195
|
+
@_permitted_filter ||= {}
|
196
|
+
end
|
197
|
+
|
198
|
+
def permitted_filter
|
199
|
+
_permitted_filter[action_name] ||=
|
200
|
+
if c = self.class.instance_variable_get(:@context)[action_name]
|
201
|
+
(c.args || []).reject { |x| x.hidden }.map {|x| x.name }
|
202
|
+
else
|
203
|
+
[model.primary_key]
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def obsolete_token
|
208
|
+
@token.update_attribute(:obsoleted_at, Time.zone.now)
|
209
|
+
end
|
210
|
+
|
211
|
+
def session_update
|
212
|
+
session.update("user" => session_data)
|
213
|
+
rescue ActiveRecord::RecordNotFound
|
214
|
+
session.update("user" => nil)
|
215
|
+
end
|
216
|
+
|
217
|
+
def current_user
|
218
|
+
@current_user ||= authenticate
|
219
|
+
end
|
220
|
+
|
221
|
+
def session_data
|
222
|
+
#@current_user&.jsonize(only: %w(id last_login_at last_active_at accounts refresh_token session_token))
|
223
|
+
|
224
|
+
@session_data ||= current_user_data.merge(serialize_collection(@session))
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
Auth::Controller = Tiun::Auth::Controller
|
data/lib/tiun/auth.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
module Tiun::Auth
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
before_action :authenticate_user
|
6
|
+
end
|
7
|
+
|
8
|
+
# TODO add automatic with list
|
9
|
+
def current_user
|
10
|
+
@current_user ||=
|
11
|
+
#if session["user"] && user = User.where(id: session["user"]["id"]).first
|
12
|
+
if session["user"] && user = User.with_user_names(auth_context).with_descriptions(auth_context).with_accounts(auth_context).where(id: session["user"]["id"]).first
|
13
|
+
session_data = serialize_collection(user.update_session(session["user"]["refresh_token"]), auth_context)
|
14
|
+
session.update("user" => session["user"].merge(session_data))
|
15
|
+
|
16
|
+
user
|
17
|
+
end
|
18
|
+
rescue Tiun::Model::Auth::InvalidTokenError
|
19
|
+
session.delete('user')
|
20
|
+
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def authenticate_user
|
25
|
+
current_user
|
26
|
+
end
|
27
|
+
|
28
|
+
def model_name
|
29
|
+
@_model_name ||= self.class.to_s.gsub(/.*::/, "").gsub("Controller", "").singularize
|
30
|
+
end
|
31
|
+
|
32
|
+
def model
|
33
|
+
@_model ||= model_name.constantize
|
34
|
+
rescue NameError
|
35
|
+
User
|
36
|
+
end
|
37
|
+
|
38
|
+
def get_properties for_object = nil
|
39
|
+
@get_properties ||=
|
40
|
+
if k = self.class.instance_variable_get(:@context)&.[](action_name)&.kind
|
41
|
+
Tiun.type_attributes_for(k)
|
42
|
+
else
|
43
|
+
case for_object
|
44
|
+
when ActiveRecord::Reflection, ActiveRecord::Associations, ActiveRecord::Scoping, ActiveRecord::Base
|
45
|
+
for_object.model
|
46
|
+
when Array
|
47
|
+
for_object.find { |x| x.respond_to?(:attribute_types) } ||
|
48
|
+
for_object.find { |x| x.class.respond_to?(:attribute_types) }.class
|
49
|
+
when Hash
|
50
|
+
for_object.values.find { |x| x.respond_to?(:attribute_types) } ||
|
51
|
+
for_object.values.find { |x| x.class.respond_to?(:attribute_types) }.class
|
52
|
+
when NilClass
|
53
|
+
model
|
54
|
+
else
|
55
|
+
for_object
|
56
|
+
end.attribute_types.keys
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def auth_context
|
61
|
+
@auth_context ||= { except: supplement_names, locales: locales }
|
62
|
+
end
|
63
|
+
|
64
|
+
def context
|
65
|
+
@context ||= { except: supplement_names, locales: locales }
|
66
|
+
end
|
67
|
+
|
68
|
+
def locales
|
69
|
+
@locales ||= [I18n.locale]
|
70
|
+
end
|
71
|
+
|
72
|
+
def supplement_names
|
73
|
+
%i(created_at updated_at tsv)
|
74
|
+
end
|
75
|
+
|
76
|
+
def serialize_collection collection, context = self.context
|
77
|
+
collection.as_json(context.merge(only: get_properties(collection)))
|
78
|
+
end
|
79
|
+
|
80
|
+
def serialize object, context = self.context
|
81
|
+
object.as_json(context.merge(only: get_properties(object)))
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
Auth = Tiun::Auth
|
@@ -1,9 +1,11 @@
|
|
1
1
|
require 'tiun/base'
|
2
2
|
require 'tiun/core_helper'
|
3
3
|
|
4
|
-
|
4
|
+
<% base = object_in ? nil : " < #{base_controller}" %>
|
5
|
+
|
6
|
+
class ::<%= object_name %><%= base %>
|
5
7
|
include ::Tiun::CoreHelper
|
6
|
-
include
|
8
|
+
include ::<%= template_controller_for(context) %>
|
7
9
|
|
8
10
|
<% if action_names = action_names_for(context) %>
|
9
11
|
@context = (@context || {}).merge(YAML.load("<%= action_names.to_yaml.gsub('"', '\"') %>", permitted_classes: [OpenStruct, Symbol]))
|
data/lib/tiun/automodel.rb.erb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
require 'tiun/model'
|
2
2
|
|
3
|
-
class ::<%= object_name %><%= object_in ? nil : " < " + base_model.to_s %>
|
3
|
+
class ::<%= object_name %><%= object_in || !table_exist?(object_name) ? nil : " < " + base_model(object_name).to_s %>
|
4
|
+
<% unless table_exist?(object_name) %>
|
5
|
+
include <%= base_model(object_name).to_s %>
|
6
|
+
<% end %>
|
4
7
|
extend ::Tiun::Model
|
5
8
|
|
6
9
|
tiun
|
data/lib/tiun/base.rb
CHANGED
@@ -2,12 +2,14 @@ require 'active_support'
|
|
2
2
|
|
3
3
|
module Tiun::Base
|
4
4
|
class OutOfRangeError < StandardError; end
|
5
|
+
class InvalidParamNameError < StandardError; end
|
5
6
|
|
6
7
|
extend ActiveSupport::Concern
|
7
8
|
|
8
9
|
included do
|
9
|
-
|
10
|
+
include Tiun::Auth
|
10
11
|
|
12
|
+
attr_reader :object
|
11
13
|
|
12
14
|
#attr_accessor :params
|
13
15
|
#define_callbacks :subscribe
|
@@ -21,16 +23,18 @@ module Tiun::Base
|
|
21
23
|
# I18n.with_locale(I18n.locale.to_s, &block)
|
22
24
|
# include Pundit
|
23
25
|
|
24
|
-
|
26
|
+
before_action :set_languages, if: ->{ respond_to?(:set_languages) }
|
27
|
+
before_action :authenticate_user
|
25
28
|
# before_action :set_tokens, only: %i(index)
|
26
29
|
# before_action :set_page, only: %i(index)
|
27
30
|
# before_action :set_locales
|
28
31
|
before_action :new_object, only: %i(create)
|
29
32
|
before_action :fetch_object, only: %i(show update destroy)
|
30
33
|
before_action :fetch_objects, only: %i(index)
|
34
|
+
before_action :fetch_ac_objects, only: %i(ac)
|
31
35
|
before_action :parse_range, only: %i(index ac)
|
32
36
|
after_action :return_range, only: %i(index ac)
|
33
|
-
|
37
|
+
before_action :authorize!
|
34
38
|
|
35
39
|
rescue_from Exception, with: :exception
|
36
40
|
rescue_from ActionView::MissingTemplate, with: :missing_template
|
@@ -57,7 +61,7 @@ module Tiun::Base
|
|
57
61
|
end
|
58
62
|
end
|
59
63
|
|
60
|
-
# POST /<objects
|
64
|
+
# POST /<objects>.json
|
61
65
|
def create
|
62
66
|
object.save!
|
63
67
|
|
@@ -93,13 +97,14 @@ module Tiun::Base
|
|
93
97
|
|
94
98
|
# DELETE /<objects>/:id.json
|
95
99
|
def destroy
|
100
|
+
answer = serialize(object)
|
96
101
|
object.destroy
|
97
102
|
|
98
103
|
respond_to do |format|
|
99
104
|
format.html
|
100
|
-
format.json { render json:
|
105
|
+
format.json { render json: answer }
|
101
106
|
format.jsonp { head :ok }
|
102
|
-
format.any { render json:
|
107
|
+
format.any { render json: answer, content_type: 'application/json' }
|
103
108
|
end
|
104
109
|
rescue Exception
|
105
110
|
binding.pry
|
@@ -107,8 +112,6 @@ module Tiun::Base
|
|
107
112
|
|
108
113
|
# GET /<objects>/ac.json
|
109
114
|
def ac
|
110
|
-
@objects = apply_scopes(model)
|
111
|
-
|
112
115
|
respond_to do |format|
|
113
116
|
format.json {
|
114
117
|
render json: {
|
@@ -131,18 +134,10 @@ module Tiun::Base
|
|
131
134
|
total == 0 ? 204 : total > range.end + 1 ? 206 : 200
|
132
135
|
end
|
133
136
|
|
134
|
-
def model_name
|
135
|
-
@_model_name ||= self.class.to_s.gsub(/.*::/, "").gsub("Controller", "").singularize
|
136
|
-
end
|
137
|
-
|
138
137
|
def param_name
|
139
138
|
@_param_name ||= model_name.tableize.singularize
|
140
139
|
end
|
141
140
|
|
142
|
-
def model
|
143
|
-
@_model ||= model_name.constantize
|
144
|
-
end
|
145
|
-
|
146
141
|
# def serializer
|
147
142
|
# @serializer ||= "#{model_name}Serializer".constantize
|
148
143
|
# end
|
@@ -159,10 +154,6 @@ module Tiun::Base
|
|
159
154
|
# list_serializer.new(model: model)
|
160
155
|
# end
|
161
156
|
#
|
162
|
-
def apply_scopes model
|
163
|
-
model
|
164
|
-
end
|
165
|
-
|
166
157
|
# def policy_name
|
167
158
|
# @policy_name ||= Object.const_get(model.name + "Policy")
|
168
159
|
# rescue NameError
|
@@ -178,56 +169,66 @@ module Tiun::Base
|
|
178
169
|
end
|
179
170
|
|
180
171
|
def unprocessable_entity e
|
181
|
-
errors = @object && @object.errors.any? && @object.errors || e.to_s
|
172
|
+
errors = @object && @object.errors.any? && @object.errors.to_a || [e.to_s]
|
182
173
|
args = params.permit(*permitted_filter).to_h
|
183
174
|
render json: { args: args, error: errors }, status: :unprocessable_entity
|
184
175
|
end
|
185
176
|
|
186
177
|
def parameter_missing e
|
187
|
-
|
178
|
+
errors = ["[#{e.class}]{parameter_missing}> #{e.message} \n\t #{e.backtrace.join("\n\t")}"]
|
188
179
|
args = params.permit(*permitted_filter).to_h
|
189
|
-
render json: { args: args, error:
|
180
|
+
render json: { args: args, error: errors }, status: 500
|
190
181
|
end
|
191
182
|
|
192
183
|
def missing_template e
|
193
|
-
|
184
|
+
errors = ["[#{e.class}]{missing_template}> #{e.message} \n\t #{e.backtrace.join("\n\t")}"]
|
194
185
|
args = params.permit(*permitted_filter).to_h
|
195
|
-
render json: { args: args, error:
|
186
|
+
render json: { args: args, error: errors }, status: 500
|
196
187
|
end
|
197
188
|
|
198
189
|
def exception e
|
199
|
-
|
190
|
+
errors = ["[#{e.class}]{exception}> #{e.message} \n\t #{e.backtrace.join("\n\t")}"]
|
200
191
|
args = params.permit(*permitted_filter).to_h
|
201
|
-
render json: { args: args, error:
|
192
|
+
render json: { args: args, error: errors }, status: 500
|
202
193
|
end
|
203
194
|
|
204
195
|
def ac_limit
|
205
196
|
500
|
206
197
|
end
|
207
198
|
|
208
|
-
def context
|
209
|
-
@_context ||= { except: supplement_names, locales: locales }
|
210
|
-
end
|
211
|
-
|
212
199
|
def ac_context
|
213
200
|
@_ac_context ||= { only: %i(key value) }
|
214
201
|
end
|
215
202
|
|
216
|
-
def
|
203
|
+
def tiun_context
|
204
|
+
self.class.instance_variable_get(:@context) || {}
|
205
|
+
end
|
206
|
+
|
207
|
+
def tiun_args
|
208
|
+
tiun_context[action_name]&.args || []
|
209
|
+
end
|
210
|
+
|
211
|
+
def _permitted_filter
|
217
212
|
@_permitted_filter ||= {}
|
218
|
-
|
219
|
-
|
220
|
-
|
213
|
+
end
|
214
|
+
|
215
|
+
def permitted_filter
|
216
|
+
_permitted_filter[action_name] ||=
|
217
|
+
if c = tiun_context[action_name]
|
218
|
+
(c.args || []).reject { |x| x.hidden }.map {|x| x.name }
|
221
219
|
else
|
222
220
|
[model.primary_key]
|
223
221
|
end
|
224
222
|
end
|
225
223
|
|
226
|
-
def
|
224
|
+
def _permitted_self
|
227
225
|
@_permitted_self ||= {}
|
228
|
-
|
229
|
-
|
230
|
-
|
226
|
+
end
|
227
|
+
|
228
|
+
def permitted_self
|
229
|
+
_permitted_self[action_name] ||=
|
230
|
+
if c = tiun_context[action_name]
|
231
|
+
(c.args || []).reject { |x| x.hidden }.map {|x| x.kind == 'json' ? {x.name => {}} : x.name }
|
231
232
|
else
|
232
233
|
model.attribute_types.keys
|
233
234
|
end
|
@@ -239,7 +240,7 @@ module Tiun::Base
|
|
239
240
|
child_model = model.reflections[name.to_s].klass
|
240
241
|
value = child_model.attribute_types.keys
|
241
242
|
value << :_destroy if opts[:allow_destroy]
|
242
|
-
res[name] = value - supplement_names.map(&:to_s)
|
243
|
+
res["#{name}_attributes"] = value - supplement_names.map(&:to_s)
|
243
244
|
|
244
245
|
res
|
245
246
|
end
|
@@ -249,34 +250,28 @@ module Tiun::Base
|
|
249
250
|
params.require(param_name).permit(*permitted_self, **permitted_children)
|
250
251
|
end
|
251
252
|
|
252
|
-
def supplement_names
|
253
|
-
%i(created_at updated_at tsv)
|
254
|
-
end
|
255
|
-
|
256
253
|
def total
|
257
|
-
|
258
|
-
|
259
|
-
|
254
|
+
@total ||=
|
255
|
+
%i(total_size total_count count).reduce(nil) do |count, method|
|
256
|
+
objects.respond_to?(method) && !count ? objects.send(method) : count
|
257
|
+
end || raise
|
260
258
|
end
|
261
259
|
|
262
260
|
def paged_objects
|
263
261
|
objects.range(range)
|
264
262
|
end
|
265
263
|
|
266
|
-
def render_type type
|
267
|
-
end
|
268
|
-
|
269
264
|
def get_properties
|
270
265
|
@get_properties ||=
|
271
|
-
if k =
|
272
|
-
Tiun.type_attributes_for(k)
|
266
|
+
if k = tiun_context[action_name]&.kind
|
267
|
+
Tiun.type_attributes_for(k, %i(read write))
|
273
268
|
else
|
274
269
|
model.attribute_types.keys
|
275
270
|
end
|
276
271
|
end
|
277
272
|
|
278
|
-
def serialize_collection collection
|
279
|
-
collection.jsonize(context.merge(only: get_properties))
|
273
|
+
def serialize_collection collection, context_in = {}
|
274
|
+
collection.jsonize(context.merge(context_in).merge(only: get_properties))
|
280
275
|
# format.json { render :index, json: objects, locales: locales,
|
281
276
|
# serializer: objects_serializer,
|
282
277
|
# each_serializer: serializer,
|
@@ -290,28 +285,23 @@ module Tiun::Base
|
|
290
285
|
# format.json { render :show, json: @object, locales: @locales,
|
291
286
|
# serializer: serializer }
|
292
287
|
end
|
293
|
-
#
|
294
288
|
|
295
289
|
def authorize!
|
296
|
-
|
297
|
-
|
290
|
+
pol = policy.new(current_user, @object)
|
291
|
+
|
292
|
+
if pol.respond_to?(action_name + "?") && !pol.send(action_name + "?") ||
|
293
|
+
pol.respond_to?(:match?) && !pol.match?(allowing_permission)
|
298
294
|
raise Tiun::Policy::NotAuthorizedError, "not allowed to do #{action_name} this #{@object.inspect}"
|
299
295
|
end
|
300
296
|
end
|
301
297
|
|
302
298
|
def per
|
303
299
|
@per ||= (params[:per] ||
|
304
|
-
|
305
|
-
args.reduce(nil) { |res, x| res || x.name == "per" && x.default || nil }
|
306
|
-
end || 25).to_i
|
300
|
+
tiun_args.reduce(nil) { |res, x| res || x.name == "per" && x.default || nil } || 25).to_i
|
307
301
|
end
|
308
302
|
|
309
303
|
def page
|
310
|
-
@page ||= (params[:page] || 1).to_i
|
311
|
-
end
|
312
|
-
|
313
|
-
def locales
|
314
|
-
@locales ||= [I18n.locale]
|
304
|
+
@page ||= (params[:page] || params[:p] || 1).to_i
|
315
305
|
end
|
316
306
|
|
317
307
|
def set_tokens
|
@@ -328,6 +318,16 @@ module Tiun::Base
|
|
328
318
|
@range
|
329
319
|
end
|
330
320
|
|
321
|
+
def allowing_permission
|
322
|
+
if perm = tiun_context[action_name].policy
|
323
|
+
[perm].flatten.map { |x| x.split(",") }.flatten
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
def objects
|
328
|
+
@objects ||= with_others(objects_scope)
|
329
|
+
end
|
330
|
+
|
331
331
|
# callbacks
|
332
332
|
def parse_range
|
333
333
|
@range =
|
@@ -344,27 +344,61 @@ module Tiun::Base
|
|
344
344
|
@object = model.new(permitted_params)
|
345
345
|
end
|
346
346
|
|
347
|
+
def fetch_object
|
348
|
+
@object = object_scope.first || raise(ActiveRecord::RecordNotFound)
|
349
|
+
end
|
350
|
+
|
347
351
|
def fetch_objects
|
348
|
-
@objects =
|
352
|
+
@objects = objects
|
349
353
|
end
|
350
354
|
|
351
|
-
def
|
352
|
-
|
355
|
+
def fetch_ac_objects
|
356
|
+
@objects = apply_scopes(model)
|
357
|
+
end
|
358
|
+
|
359
|
+
def object_scope
|
360
|
+
apply_scope(model, [default_arg || model.primary_key])
|
361
|
+
end
|
362
|
+
|
363
|
+
def objects_scope
|
364
|
+
apply_scope(model, tiun_args.map { |a| a.name })
|
365
|
+
end
|
366
|
+
|
367
|
+
def apply_scope model = self.model, args = []
|
368
|
+
args.reduce(model) do |rel, arg|
|
369
|
+
next rel unless params.include?(arg)
|
370
|
+
|
371
|
+
if model.respond_to?("by_#{arg}")
|
372
|
+
rel.send("by_#{arg}", params[arg])
|
373
|
+
elsif model.respond_to?("by_#{arg.alias}")
|
374
|
+
rel.send("by_#{arg.alias}", params[arg])
|
375
|
+
elsif model.attribute_names.include?(arg.alias || arg.name)
|
376
|
+
rel.where({ arg => params[arg] })
|
377
|
+
else
|
378
|
+
raise InvalidParamNameError.new("Not valid rule to fetch object by #{arg_name} argument")
|
379
|
+
end
|
380
|
+
end
|
381
|
+
end
|
353
382
|
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
383
|
+
def with_others rela
|
384
|
+
k = tiun_context[action_name]&.kind
|
385
|
+
|
386
|
+
Tiun.sublings_for(k, :write).reduce(rela) do |r, (sub, props)|
|
387
|
+
if r.respond_to?("with_#{sub}")
|
388
|
+
r.send("with_#{sub}", context.merge(only: props))
|
359
389
|
else
|
360
|
-
|
361
|
-
|
390
|
+
#TODO raise?
|
391
|
+
r
|
392
|
+
end
|
393
|
+
end
|
362
394
|
end
|
363
395
|
|
364
396
|
def return_range
|
397
|
+
response.headers["Access-Control-Expose-Headers"] = "Content-Range"
|
365
398
|
response.headers["Accept-Ranges"] = "records"
|
366
399
|
response.headers["Content-Range"] = "records #{range.begin}-#{range.end}/#{total}"
|
367
|
-
|
400
|
+
# NOTE: HTTP isn't supported range except bytes https://stackoverflow.com/a/9480391/446267
|
401
|
+
# response.headers["Content-Length"] = [range.end + 1, total].min - range.begin
|
368
402
|
end
|
369
403
|
end
|
370
404
|
|
data/lib/tiun/core_helper.rb
CHANGED
@@ -8,6 +8,14 @@ module ::Tiun::CoreHelper
|
|
8
8
|
})
|
9
9
|
content_tag(:div, '', html_options, &block)
|
10
10
|
end
|
11
|
+
|
12
|
+
def current_user_data
|
13
|
+
@current_user&.jsonize(only:
|
14
|
+
["id", "last_login_at", "last_active_at", "default_name", "refresh_token", "session_token",
|
15
|
+
"accounts" => %w(id no type),
|
16
|
+
"user_names" => %w(id kind way usage source main nomen_id name),
|
17
|
+
"descriptions" => %w(id language_code alphabeth_code type text)])
|
18
|
+
end
|
11
19
|
end
|
12
20
|
|
13
21
|
CoreHelper = ::Tiun::CoreHelper
|
data/lib/tiun/engine.rb
CHANGED
@@ -25,6 +25,7 @@ class Tiun::Engine < ::Rails::Engine
|
|
25
25
|
|
26
26
|
# config.autoload_paths += %W(#{Tiun::Engine.root}/lib/tiun/autoloads)
|
27
27
|
# config.autoload_paths += %W(#{Tiun::Engine.root}/lib/tiun/views)
|
28
|
+
config.i18n.load_path += Dir[Tiun::Engine.root.join('lib', 'config', 'locale', '**', '*.{yaml,yml}')]
|
28
29
|
|
29
30
|
config.to_prepare do
|
30
31
|
::ActiveRecord::Base.extend(Tiun::Model)
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'tiun/model'
|
2
|
+
|
3
|
+
module Tiun::Model::Account
|
4
|
+
class InvalidPasswordError < StandardError; end
|
5
|
+
|
6
|
+
def authenticated_user! password
|
7
|
+
user.match_password?(password) ? user : raise(InvalidPasswordError)
|
8
|
+
end
|
9
|
+
|
10
|
+
def create_validate_token
|
11
|
+
Token::Validate.create(tokenable: self)
|
12
|
+
end
|
13
|
+
|
14
|
+
def validate_token
|
15
|
+
tokina.where(type: "Token::Validate").first
|
16
|
+
end
|
17
|
+
|
18
|
+
class << self
|
19
|
+
def included kls
|
20
|
+
kls.module_eval do
|
21
|
+
belongs_to :user
|
22
|
+
has_many :tokina, as: :tokenable
|
23
|
+
|
24
|
+
after_create :create_validate_token
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
Model::Account = Tiun::Model::Account
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'tiun/model'
|
2
|
+
|
3
|
+
module Tiun::Model::Auth
|
4
|
+
class InvalidTokenError < StandardError; end
|
5
|
+
|
6
|
+
attr_reader :password, :password_confirmation
|
7
|
+
|
8
|
+
def password_confirmation= value
|
9
|
+
@password_confirmation = value
|
10
|
+
end
|
11
|
+
|
12
|
+
def password= value
|
13
|
+
@password = value
|
14
|
+
end
|
15
|
+
|
16
|
+
def fill_in_encrypted_password
|
17
|
+
self.encrypted_password = Tiun::Model::Auth.encrypt!(password) if password and quick_match_passwords?
|
18
|
+
end
|
19
|
+
|
20
|
+
def match_password? password
|
21
|
+
self.encrypted_password == Tiun::Model::Auth.encrypt!(password)
|
22
|
+
end
|
23
|
+
|
24
|
+
def quick_match_passwords?
|
25
|
+
password && password_confirmation && password == password_confirmation
|
26
|
+
end
|
27
|
+
|
28
|
+
def match_passwords?
|
29
|
+
self.password && self.password == password_confirmation ||
|
30
|
+
self.encrypted_password && (!password_confirmation || self.match_password?(password_confirmation))
|
31
|
+
end
|
32
|
+
|
33
|
+
def generate_session
|
34
|
+
{
|
35
|
+
session_token: Token::Session.create(tokenable: self),
|
36
|
+
refresh_token: Token::Refresh.create(tokenable: self)
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
def update_session token
|
41
|
+
refresh_token =
|
42
|
+
if token.is_a?(Token::Refresh)
|
43
|
+
token
|
44
|
+
elsif token.is_a?(Hash)
|
45
|
+
Token::Refresh.actual.find_by_code(token["code"])
|
46
|
+
end || raise(InvalidTokenError.new(token))
|
47
|
+
|
48
|
+
{
|
49
|
+
session_token: Token::Session.create(tokenable: self),
|
50
|
+
refresh_token: refresh_token
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
def current_session token = nil
|
55
|
+
refresh_token = Token::Refresh.find_by(tokenable: self)
|
56
|
+
session_token = Token::Session.find_by(tokenable: self)
|
57
|
+
|
58
|
+
{ session_token: session_token, refresh_token: refresh_token }
|
59
|
+
end
|
60
|
+
|
61
|
+
def destroy_session token = nil
|
62
|
+
refresh_token = Token::Refresh.find_by(tokenable: self)
|
63
|
+
session_token = Token::Session.find_by(tokenable: self)
|
64
|
+
|
65
|
+
{ session_token: session_token.destroy, refresh_token: refresh_token.destroy }
|
66
|
+
end
|
67
|
+
|
68
|
+
class << self
|
69
|
+
def encrypt! password
|
70
|
+
OpenSSL::HMAC.hexdigest("SHA256", "salt", password)
|
71
|
+
end
|
72
|
+
|
73
|
+
def included kls
|
74
|
+
kls.module_eval do
|
75
|
+
has_many :accounts
|
76
|
+
has_many :tokina, as: :tokenable
|
77
|
+
|
78
|
+
validates_presence_of :encrypted_password
|
79
|
+
validate :match_passwords?
|
80
|
+
before_validation :fill_in_encrypted_password
|
81
|
+
|
82
|
+
accepts_nested_attributes_for :accounts, reject_if: :all_blank, allow_destroy: true
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
Model::Auth = Tiun::Model::Auth
|
data/lib/tiun/policy.rb
CHANGED
@@ -11,32 +11,12 @@ module Tiun::Policy
|
|
11
11
|
true
|
12
12
|
end
|
13
13
|
|
14
|
-
def all?
|
15
|
-
default?
|
16
|
-
end
|
17
|
-
|
18
|
-
def index?
|
19
|
-
default?
|
20
|
-
end
|
21
|
-
|
22
14
|
def show?
|
23
15
|
scope.where(id: record.id).exists?
|
24
16
|
end
|
25
17
|
|
26
|
-
def
|
27
|
-
|
28
|
-
end
|
29
|
-
|
30
|
-
def new?
|
31
|
-
default?
|
32
|
-
end
|
33
|
-
|
34
|
-
def update?
|
35
|
-
default?
|
36
|
-
end
|
37
|
-
|
38
|
-
def destroy?
|
39
|
-
default?
|
18
|
+
def match? allowing_permissions
|
19
|
+
(user.permissions & allowing_permissions).any?
|
40
20
|
end
|
41
21
|
|
42
22
|
def scope
|
data/lib/tiun/version.rb
CHANGED
data/lib/tiun.rb
CHANGED
@@ -11,6 +11,7 @@ require "tiun/version"
|
|
11
11
|
require "tiun/migration"
|
12
12
|
require "tiun/attributes"
|
13
13
|
require "tiun/base"
|
14
|
+
require "tiun/auth"
|
14
15
|
require "tiun/core_helper"
|
15
16
|
|
16
17
|
module Tiun
|
@@ -118,9 +119,7 @@ module Tiun
|
|
118
119
|
end
|
119
120
|
|
120
121
|
def controller_default_arg_for context
|
121
|
-
/:(?<arg>[^\.]+)
|
122
|
-
|
123
|
-
arg
|
122
|
+
context.key || /:(?<arg>[^\.]+)/.match(context.path)&.[](:arg)
|
124
123
|
end
|
125
124
|
|
126
125
|
def route_title_for context
|
@@ -159,21 +158,37 @@ module Tiun
|
|
159
158
|
end unless type_names.blank?
|
160
159
|
end
|
161
160
|
|
161
|
+
# generates a hash to collect all subling relation for the specificed kind type
|
162
|
+
#
|
163
|
+
def sublings_for type_name_in, kind_in = %i(read write)
|
164
|
+
kind = [kind_in].flatten
|
165
|
+
|
166
|
+
type_attributes_for(type_name_in, kind).reduce({}) do |res, value_in|
|
167
|
+
if value_in.is_a?(Hash)
|
168
|
+
res.merge(value_in)
|
169
|
+
else
|
170
|
+
res
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
162
175
|
# +type_attributes_for+ renders attributes array for the type name or type itself specified,
|
163
176
|
# if no type name has been found, it returns a blank array.
|
164
177
|
#
|
165
|
-
def type_attributes_for type_name_in
|
178
|
+
def type_attributes_for type_name_in, kind = %i(read write)
|
166
179
|
type = type_name_in.is_a?(OpenStruct) ? type_name_in : find_type(type_name_in)
|
167
180
|
|
168
181
|
return [] unless type
|
169
182
|
|
170
183
|
type.fields.map do |x|
|
184
|
+
next nil unless x.to_h.keys.select { |y| /only$/ =~ y }.reduce(kind) { |k, prop| x[prop] ? ["#{prop}".sub("only","").to_sym] & k : k }.any?
|
185
|
+
|
171
186
|
if sub = Tiun.find_type(x.kind)
|
172
|
-
{ x.name => type_attributes_for(sub) }
|
187
|
+
{ x.name => type_attributes_for(sub, kind) }
|
173
188
|
else
|
174
189
|
x.name
|
175
190
|
end
|
176
|
-
end
|
191
|
+
end.compact
|
177
192
|
end
|
178
193
|
|
179
194
|
def detect_type type_in
|
@@ -194,9 +209,10 @@ module Tiun
|
|
194
209
|
method_name = method.name
|
195
210
|
rule = MAP[method_name]
|
196
211
|
|
197
|
-
action =
|
198
|
-
|
199
|
-
|
212
|
+
action =
|
213
|
+
method.action || (rule.is_a?(String) && rule || rule.reduce(nil) do |a, (re, action)|
|
214
|
+
a || context.path =~ re && action || nil
|
215
|
+
end)
|
200
216
|
|
201
217
|
# TODO validated types
|
202
218
|
if ! action
|
@@ -235,7 +251,7 @@ module Tiun
|
|
235
251
|
rescue NoMethodError
|
236
252
|
error :no_resources_section_defined_in_config, {config: config, default: default}
|
237
253
|
|
238
|
-
|
254
|
+
default
|
239
255
|
end
|
240
256
|
|
241
257
|
def load_defaults_from config
|
@@ -378,12 +394,24 @@ module Tiun
|
|
378
394
|
rescue NameError
|
379
395
|
end
|
380
396
|
|
381
|
-
def
|
382
|
-
|
397
|
+
def table_exist? name
|
398
|
+
ActiveRecord::Base.connection.data_source_exists?(name.to_s.tableize)
|
399
|
+
end
|
400
|
+
|
401
|
+
def base_model name = nil
|
402
|
+
@base_model ||= table_exist?(name) ? ActiveRecord::Base : ActiveModel::Model
|
383
403
|
end
|
384
404
|
|
385
405
|
def base_controller
|
386
|
-
@base_controller ||= ActionController::Base
|
406
|
+
@base_controller ||= defined?(ApplicationController) ? ApplicationController : ActionController::Base
|
407
|
+
end
|
408
|
+
|
409
|
+
def template_controller_for context
|
410
|
+
t = context.template&.camelize
|
411
|
+
|
412
|
+
self.const_get(t).const_get(:Controller)
|
413
|
+
rescue NameError, TypeError
|
414
|
+
::Tiun::Base
|
387
415
|
end
|
388
416
|
|
389
417
|
def error code, options
|
@@ -486,6 +514,5 @@ module Tiun
|
|
486
514
|
# end
|
487
515
|
end
|
488
516
|
|
489
|
-
require "tiun/base"
|
490
517
|
require "tiun/policy"
|
491
518
|
require "tiun/engine"
|
data/tiun.gemspec
CHANGED
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
|
|
11
11
|
|
12
12
|
spec.summary = %q{Tiun is a backend gem for admin rails application.}
|
13
13
|
spec.description = %q{Tiun is an old russian high level manager for a city from a prince or boyar. According the repo tiun is a backend gem for admin rails application.}
|
14
|
-
spec.homepage = "https://
|
14
|
+
spec.homepage = "https://codeberg.org/znamenica/tiun"
|
15
15
|
spec.license = "MIT"
|
16
16
|
|
17
17
|
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tiun
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Malo Skrylevo
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-05-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -290,12 +290,15 @@ files:
|
|
290
290
|
- bin/console
|
291
291
|
- bin/setup
|
292
292
|
- exe/tiun
|
293
|
+
- lib/config/locale/ru.yml
|
293
294
|
- lib/config/routes.rb
|
294
295
|
- lib/layouts/tiun.html.erb
|
295
296
|
- lib/tiun.rb
|
296
297
|
- lib/tiun/actor.rb
|
297
298
|
- lib/tiun/actor/validate.rb
|
298
299
|
- lib/tiun/attributes.rb
|
300
|
+
- lib/tiun/auth.rb
|
301
|
+
- lib/tiun/auth/controller.rb
|
299
302
|
- lib/tiun/autocontroller.rb.erb
|
300
303
|
- lib/tiun/autolistserializer.rb.erb
|
301
304
|
- lib/tiun/automigration.rb.erb
|
@@ -312,11 +315,14 @@ files:
|
|
312
315
|
- lib/tiun/migration.rb
|
313
316
|
- lib/tiun/mixins.rb
|
314
317
|
- lib/tiun/model.rb
|
318
|
+
- lib/tiun/model/account.rb
|
319
|
+
- lib/tiun/model/auth.rb
|
320
|
+
- lib/tiun/model/token.rb
|
315
321
|
- lib/tiun/policy.rb
|
316
322
|
- lib/tiun/serializer.rb
|
317
323
|
- lib/tiun/version.rb
|
318
324
|
- tiun.gemspec
|
319
|
-
homepage: https://
|
325
|
+
homepage: https://codeberg.org/znamenica/tiun
|
320
326
|
licenses:
|
321
327
|
- MIT
|
322
328
|
metadata:
|