tiun 0.0.1 → 0.0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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: 9c21b8caf4fec1239c639b03b0f3c62f1ed7af16a0c94d0807c755755465d149
|
4
|
+
data.tar.gz: 8a15038e16fb0a0886b157af80acd7310a96cefb45c2770353c2615e0a3ec57b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5cf8adc384fe3b1221108245987724777c91a372a84a1b221ec724ba45a872930ea02f18f1f9ce92e035878899517d8eb89c4ba4aa55b62a602398e12693f62d
|
7
|
+
data.tar.gz: 457772ee88082dcaaf646a6faf32081e77d9b60a973c8b9376fcea8ed3c829bc89684abe543f850fc30e6dd4037a6a13929bad89deb0beb2af3fc74a9b5ad417
|
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 && (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.1
|
4
|
+
version: 0.0.2.1
|
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-31 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:
|