infopark_reactor 1.5.1
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.
- 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
|