infopark_reactor 1.5.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/Gemfile +4 -0
- data/LICENSE +165 -0
- data/README.md +142 -0
- data/Rakefile +25 -0
- data/app/controllers/reactor_controller.rb +53 -0
- data/config/routes.rb +6 -0
- data/infopark_reactor.gemspec +30 -0
- data/lib/engine.rb +35 -0
- data/lib/infopark_reactor.rb +38 -0
- data/lib/reactor/already_released.rb +3 -0
- data/lib/reactor/attributes.rb +365 -0
- data/lib/reactor/attributes/date_serializer.rb +35 -0
- data/lib/reactor/attributes/html_serializer.rb +31 -0
- data/lib/reactor/attributes/link_list_extender.rb +50 -0
- data/lib/reactor/attributes/link_list_serializer.rb +27 -0
- data/lib/reactor/cache/permission.rb +36 -0
- data/lib/reactor/cache/user.rb +37 -0
- data/lib/reactor/legacy.rb +60 -0
- data/lib/reactor/link/external.rb +19 -0
- data/lib/reactor/link/internal.rb +19 -0
- data/lib/reactor/link/temporary_link.rb +43 -0
- data/lib/reactor/no_working_version.rb +1 -0
- data/lib/reactor/not_permitted.rb +1 -0
- data/lib/reactor/permission.rb +262 -0
- data/lib/reactor/persistence.rb +473 -0
- data/lib/reactor/rc_independent.rb +13 -0
- data/lib/reactor/session.rb +48 -0
- data/lib/reactor/session/observers.rb +32 -0
- data/lib/reactor/session/user.rb +22 -0
- data/lib/reactor/streaming_upload.rb +22 -0
- data/lib/reactor/sudo.rb +11 -0
- data/lib/reactor/support/link_matcher.rb +44 -0
- data/lib/reactor/tools/workflow_generator.rb +195 -0
- data/lib/reactor/validations.rb +55 -0
- data/lib/reactor/version.rb +3 -0
- data/lib/reactor/workflow.rb +19 -0
- data/lib/reactor/workflow/empty.rb +27 -0
- data/lib/reactor/workflow/standard.rb +34 -0
- metadata +204 -0
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'observer'
|
3
|
+
|
4
|
+
require 'reactor/cm/bridge'
|
5
|
+
require 'reactor/cache/user'
|
6
|
+
|
7
|
+
class Reactor::Session
|
8
|
+
attr_reader :user_name, :session_id
|
9
|
+
include Singleton
|
10
|
+
include Observable
|
11
|
+
|
12
|
+
def login(session_id)
|
13
|
+
if !logged_in?(session_id)
|
14
|
+
self.user_name = authenticate(session_id)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def destroy
|
19
|
+
self.session_id = self.user_name = nil
|
20
|
+
end
|
21
|
+
|
22
|
+
def logged_in?(session_id)
|
23
|
+
self.session_id.present? && self.user? && self.session_id == session_id
|
24
|
+
end
|
25
|
+
|
26
|
+
def user?
|
27
|
+
self.user_name.present?
|
28
|
+
end
|
29
|
+
|
30
|
+
def user
|
31
|
+
Reactor::Cache::User.instance.get(self.user_name)
|
32
|
+
end
|
33
|
+
|
34
|
+
def user_name=(new_user_name)
|
35
|
+
@user_name = new_user_name
|
36
|
+
changed(true) # I will find and burn your house to the ground if you remove this line
|
37
|
+
notify_observers(@user_name)
|
38
|
+
end
|
39
|
+
|
40
|
+
protected
|
41
|
+
attr_writer :session_id
|
42
|
+
|
43
|
+
def authenticate(session_id)
|
44
|
+
self.session_id = session_id
|
45
|
+
Reactor::Cm::Bridge.login_for(session_id)
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'reactor/session'
|
2
|
+
|
3
|
+
module Reactor::Session::Observers
|
4
|
+
class PermissionCacheInvalidator
|
5
|
+
def update(user_name)
|
6
|
+
#puts '%%% invalidating permission cache'
|
7
|
+
Reactor::Cache::Permission.instance.invalidate(user_name)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class UserCacheInvalidator
|
12
|
+
def update(user_name)
|
13
|
+
#puts '%%% invalidating user cache'
|
14
|
+
Reactor::Cache::User.instance.invalidate(user_name)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class CmsAccessDataPropagator
|
19
|
+
def update(user_name)
|
20
|
+
#puts '%%% propagating user'
|
21
|
+
Reactor::Configuration.xml_access[:username] = user_name
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
self.constants.each do |possible_observer_name|
|
26
|
+
possible_observer = self.const_get(possible_observer_name)
|
27
|
+
if possible_observer.method_defined?(:update)
|
28
|
+
Reactor::Session.instance.add_observer(possible_observer.new)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'reactor/cm/user'
|
2
|
+
|
3
|
+
module Reactor
|
4
|
+
class Session
|
5
|
+
class User
|
6
|
+
def initialize(user_name)
|
7
|
+
# Rails.logger.debug "Reading user #{user_name} from CM"
|
8
|
+
user = Reactor::Cm::User.new(user_name)
|
9
|
+
@user_name= user_name
|
10
|
+
@groups = user.groups
|
11
|
+
@language = user.language
|
12
|
+
@superuser= user.is_root?
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :user_name, :groups, :language
|
16
|
+
|
17
|
+
def superuser?
|
18
|
+
@superuser == true
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Reactor
|
2
|
+
module StreamingUpload
|
3
|
+
module Base
|
4
|
+
# Uploads a file/string into a CM. Requires call to save afterwards(!)
|
5
|
+
# @param [String, IO] data_or_io
|
6
|
+
# @param [String] extension file extension
|
7
|
+
# @raise [Reactor::UploadError] raised when CM does not respond with
|
8
|
+
# streaming ticket (i.e. has not accepted the file)
|
9
|
+
# @raise [Timeout::Error] if upload to CM takes more than:
|
10
|
+
# 60 seconds for IO streaming IO or 30 seconds for memory
|
11
|
+
#
|
12
|
+
# NOTE: behavior of this method is slightly different, than the
|
13
|
+
# traditional method: this method opens a TCP connection to the CM,
|
14
|
+
# transfers the data and stores the reference (so called streaming
|
15
|
+
# ticket). You still need to call save! afterwards.
|
16
|
+
def upload(data_or_io, extension)
|
17
|
+
self.uploaded = true
|
18
|
+
Reactor::Tools::Uploader.new(crul_obj).upload(data_or_io, extension)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/reactor/sudo.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
module Reactor
|
2
|
+
module Sudo
|
3
|
+
def self.su(other_user_name, &block)
|
4
|
+
current_user_name = Reactor::Session.instance.user_name
|
5
|
+
Reactor::Session.instance.user_name = other_user_name
|
6
|
+
yield
|
7
|
+
ensure
|
8
|
+
Reactor::Session.instance.user_name = current_user_name || 'root'
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Reactor
|
2
|
+
module Support
|
3
|
+
class LinkMatcher
|
4
|
+
def initialize(url)
|
5
|
+
@url = url
|
6
|
+
end
|
7
|
+
|
8
|
+
def recognized?
|
9
|
+
match = match_url
|
10
|
+
(match[:action] == "index") &&
|
11
|
+
(match[:controller] == "rails_connector/cms_dispatch") &&
|
12
|
+
((match[:id].present? && Obj.exists?(match[:id].to_i)) ||
|
13
|
+
(match[:permalink].present? && Obj.exists?(:permalink => match[:permalink])))
|
14
|
+
rescue ActionController::RoutingError
|
15
|
+
return false
|
16
|
+
end
|
17
|
+
|
18
|
+
def rewrite_url
|
19
|
+
match = match_url
|
20
|
+
|
21
|
+
if match[:permalink].present?
|
22
|
+
append_fragment_and_query Obj.find_by_permalink(match[:permalink]).path
|
23
|
+
elsif match[:id].present?
|
24
|
+
append_fragment_and_query Obj.find(match[:id].to_i).path
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
def match_url
|
30
|
+
relative_url_root = ENV['RAILS_RELATIVE_URL_ROOT']
|
31
|
+
url = @url.clone
|
32
|
+
url.gsub!(/^#{Regexp.escape(relative_url_root)}/, '') if relative_url_root.present?
|
33
|
+
Rails.application.routes.recognize_path(url)
|
34
|
+
end
|
35
|
+
|
36
|
+
def append_fragment_and_query(obj_path)
|
37
|
+
uri = URI.parse(@url)
|
38
|
+
obj_path += "?#{uri.query}" if uri.query
|
39
|
+
obj_path += "##{uri.fragment}" if uri.fragment
|
40
|
+
obj_path
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,195 @@
|
|
1
|
+
module Reactor
|
2
|
+
module Tools
|
3
|
+
class WorkflowGenerator
|
4
|
+
attr_reader :obj, :users, :editors, :correctors, :groups, :workflow_name
|
5
|
+
|
6
|
+
def initialize(options)
|
7
|
+
@editors = options[:editors]
|
8
|
+
@correctors = options[:correctors]
|
9
|
+
@users = @editors + @correctors
|
10
|
+
@groups = personal_groups(@users)
|
11
|
+
@class_name = options[:obj_class] || generate_obj_class_name
|
12
|
+
@workflow_name = options[:workflow_name] || generate_workflow_name
|
13
|
+
@obj_name = options[:obj_name] || generate_obj_name
|
14
|
+
end
|
15
|
+
|
16
|
+
def generate!
|
17
|
+
create_groups
|
18
|
+
create_users
|
19
|
+
create_signatures
|
20
|
+
create_workflow
|
21
|
+
create_obj_class
|
22
|
+
create_obj
|
23
|
+
set_permissions
|
24
|
+
start_workflow
|
25
|
+
end
|
26
|
+
|
27
|
+
def destroy!
|
28
|
+
destroy_objs
|
29
|
+
destroy_obj_class
|
30
|
+
destroy_workflow
|
31
|
+
destroy_users
|
32
|
+
destroy_groups
|
33
|
+
destroy_signatures
|
34
|
+
end
|
35
|
+
|
36
|
+
def personal_group(user)
|
37
|
+
"#{user}_group"
|
38
|
+
end
|
39
|
+
|
40
|
+
protected
|
41
|
+
|
42
|
+
def personal_signature(user)
|
43
|
+
"#{user}_signature"
|
44
|
+
end
|
45
|
+
|
46
|
+
def personal_groups(users)
|
47
|
+
users.map {|user| personal_group(user) }
|
48
|
+
end
|
49
|
+
|
50
|
+
def create_groups
|
51
|
+
@groups.each do |group|
|
52
|
+
if Reactor::Cm::Group.exists?(group)
|
53
|
+
Reactor::Cm::Group.get(group)
|
54
|
+
else
|
55
|
+
Reactor::Cm::Group.create(:name => group)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def create_users
|
61
|
+
@users.each do |user|
|
62
|
+
if Reactor::Cm::User::Internal.exists?(user)
|
63
|
+
internal_user = Reactor::Cm::User::Internal.get(user)
|
64
|
+
else
|
65
|
+
internal_user = Reactor::Cm::User::Internal.create(user, personal_group(user))
|
66
|
+
end
|
67
|
+
|
68
|
+
internal_user.change_password('thepasswordispassword')
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def create_signatures
|
73
|
+
# hash storing users as keys and attribute names as values
|
74
|
+
@signatures = {}
|
75
|
+
@users.each do |user|
|
76
|
+
signature = personal_signature(user)
|
77
|
+
if Reactor::Cm::Attribute.exists?(signature)
|
78
|
+
#Reactor::Cm::Attribute.get(signature)
|
79
|
+
@signatures[user] = signature
|
80
|
+
else
|
81
|
+
Reactor::Cm::Attribute.create(signature, 'signature')
|
82
|
+
@signatures[user] = signature
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def create_workflow
|
88
|
+
if Reactor::Cm::Workflow.exists?(@workflow_name)
|
89
|
+
@workflow = Reactor::Cm::Workflow.get(@workflow_name)
|
90
|
+
else
|
91
|
+
@workflow = Reactor::Cm::Workflow.create(@workflow_name)
|
92
|
+
end
|
93
|
+
|
94
|
+
# set up workflow steps
|
95
|
+
@workflow.edit_groups = personal_groups(@editors)
|
96
|
+
|
97
|
+
serialized_signatures = []
|
98
|
+
@correctors.each do |corrector|
|
99
|
+
signature = {
|
100
|
+
:group => personal_group(corrector),
|
101
|
+
:attribute => @signatures[corrector]
|
102
|
+
}
|
103
|
+
serialized_signatures << signature
|
104
|
+
end
|
105
|
+
@workflow.signatures = serialized_signatures
|
106
|
+
|
107
|
+
@workflow.save!
|
108
|
+
@workflow.reload
|
109
|
+
end
|
110
|
+
|
111
|
+
def create_obj_class
|
112
|
+
@obj_class = if Reactor::Cm::ObjClass.exists?(@class_name)
|
113
|
+
Reactor::Cm::ObjClass.get(@class_name)
|
114
|
+
else
|
115
|
+
Reactor::Cm::ObjClass.create(@class_name, 'publication')
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def create_obj
|
120
|
+
@obj = Reactor::Cm::Obj.create(@obj_name, '/', @class_name)
|
121
|
+
@obj.set(:workflowName, @workflow_name)
|
122
|
+
@obj.save!
|
123
|
+
end
|
124
|
+
|
125
|
+
def set_permissions
|
126
|
+
# get RC object
|
127
|
+
@rc_obj = RailsConnector::Obj.find(@obj.obj_id)
|
128
|
+
|
129
|
+
# use nice API to set permissions
|
130
|
+
@groups.each do |group|
|
131
|
+
@rc_obj.permission.grant(:read, group)
|
132
|
+
@rc_obj.permission.grant(:write, group)
|
133
|
+
end
|
134
|
+
|
135
|
+
# Allow Dirk to release the object
|
136
|
+
@rc_obj.permission.grant(:root, personal_group(@correctors.last))
|
137
|
+
end
|
138
|
+
|
139
|
+
def start_workflow
|
140
|
+
@obj.release!
|
141
|
+
Reactor::Sudo.su(@editors.first) do
|
142
|
+
@obj.edit!
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def destroy_objs
|
147
|
+
RailsConnector::Obj.find_all_by_obj_class(@class_name).each(&:destroy)
|
148
|
+
end
|
149
|
+
|
150
|
+
def destroy_obj_class
|
151
|
+
@obj_class.delete!
|
152
|
+
end
|
153
|
+
|
154
|
+
def destroy_workflow
|
155
|
+
@workflow.delete!
|
156
|
+
end
|
157
|
+
|
158
|
+
def destroy_users
|
159
|
+
@users.each do |user|
|
160
|
+
Reactor::Cm::User::Internal.get(user).delete!
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def destroy_groups
|
165
|
+
@groups.each do |group|
|
166
|
+
Reactor::Cm::Group.get(group).delete!
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def destroy_signatures
|
171
|
+
@signatures.values.each do |attribute|
|
172
|
+
Reactor::Cm::Attribute.get(attribute).delete!
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def generate_workflow_name
|
177
|
+
"GeneratedWorkflow#{generate_token}"
|
178
|
+
end
|
179
|
+
|
180
|
+
def generate_obj_name
|
181
|
+
"generated_obj#{generate_token}"
|
182
|
+
end
|
183
|
+
|
184
|
+
def generate_obj_class_name
|
185
|
+
"GeneratedObjClass#{generate_token}"
|
186
|
+
end
|
187
|
+
|
188
|
+
def generate_token
|
189
|
+
characters = ('0'..'9').to_a + ('A'..'Z').to_a + ('a'..'z').to_a
|
190
|
+
token = 8.times.map { characters[rand(characters.length)] }.join
|
191
|
+
end
|
192
|
+
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Reactor
|
2
|
+
# This module provides standard validations for Objs (:permalink, :parent_obj_id, :name, :obj_class),
|
3
|
+
# and presence validations for all mandatory fields.
|
4
|
+
# It also creates new validation context :release
|
5
|
+
#
|
6
|
+
# @todo :in validations for (multi)enums
|
7
|
+
# @todo link validations?
|
8
|
+
module Validations
|
9
|
+
module Base
|
10
|
+
def self.included(base)
|
11
|
+
base.extend(ClassMethods)
|
12
|
+
# Common validations for all Objs
|
13
|
+
base.class_eval do
|
14
|
+
validates :permalink, :format => { :with => /^[-_$.\/a-zA-Z0-9]*$/ }
|
15
|
+
validates :parent_obj_id, :numericality => { :only_integer => true }, :on => :create
|
16
|
+
validates :name, :presence => true, :on => :create
|
17
|
+
validates :obj_class, :presence => true, :on => :create
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Wraps around Reactor::Persistence::Base#release! and validates object
|
22
|
+
# in :release context before release. Raises exception when invalid.
|
23
|
+
# @raise [ActiveRecord::RecordInvalid] validations registered for :release failed
|
24
|
+
def release!
|
25
|
+
raise(ActiveRecord::RecordInvalid.new(self)) unless valid?(:release)
|
26
|
+
return super
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
module ClassMethods
|
31
|
+
def inherited(subclass)
|
32
|
+
super(subclass) # if you remove this line, y'll get TypeError: can't dup NilClass at some point
|
33
|
+
|
34
|
+
# Add validation for each mandatory attribute
|
35
|
+
mandatory_attrs = __mandatory_cms_attributes(subclass.name)
|
36
|
+
mandatory_attrs.each do |attr|
|
37
|
+
subclass.send(:validates_presence_of, attr.to_sym, :on => :release)
|
38
|
+
end if mandatory_attrs
|
39
|
+
|
40
|
+
cms_attributes = __cms_attributes(subclass).values
|
41
|
+
# Add validation for linklist & multienum [minSize/maxSize]
|
42
|
+
array_attributes= cms_attributes.select {|attr| ["linklist", "multienum"].include?(attr.attribute_type) }
|
43
|
+
array_attributes.each do |attr|
|
44
|
+
length_hash = {}
|
45
|
+
length_hash[:minimum] = attr.min_size if attr.min_size && "linklist" != attr.attribute_type # CMS ignores minimum for linklists.
|
46
|
+
length_hash[:maximum] = attr.max_size if attr.max_size
|
47
|
+
|
48
|
+
subclass.send(:validates, attr.attribute_name.to_sym, :length => length_hash, :on => :release) unless length_hash.empty?
|
49
|
+
end
|
50
|
+
|
51
|
+
subclass
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|