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.
Files changed (40) hide show
  1. data/.gitignore +5 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE +165 -0
  4. data/README.md +142 -0
  5. data/Rakefile +25 -0
  6. data/app/controllers/reactor_controller.rb +53 -0
  7. data/config/routes.rb +6 -0
  8. data/infopark_reactor.gemspec +30 -0
  9. data/lib/engine.rb +35 -0
  10. data/lib/infopark_reactor.rb +38 -0
  11. data/lib/reactor/already_released.rb +3 -0
  12. data/lib/reactor/attributes.rb +365 -0
  13. data/lib/reactor/attributes/date_serializer.rb +35 -0
  14. data/lib/reactor/attributes/html_serializer.rb +31 -0
  15. data/lib/reactor/attributes/link_list_extender.rb +50 -0
  16. data/lib/reactor/attributes/link_list_serializer.rb +27 -0
  17. data/lib/reactor/cache/permission.rb +36 -0
  18. data/lib/reactor/cache/user.rb +37 -0
  19. data/lib/reactor/legacy.rb +60 -0
  20. data/lib/reactor/link/external.rb +19 -0
  21. data/lib/reactor/link/internal.rb +19 -0
  22. data/lib/reactor/link/temporary_link.rb +43 -0
  23. data/lib/reactor/no_working_version.rb +1 -0
  24. data/lib/reactor/not_permitted.rb +1 -0
  25. data/lib/reactor/permission.rb +262 -0
  26. data/lib/reactor/persistence.rb +473 -0
  27. data/lib/reactor/rc_independent.rb +13 -0
  28. data/lib/reactor/session.rb +48 -0
  29. data/lib/reactor/session/observers.rb +32 -0
  30. data/lib/reactor/session/user.rb +22 -0
  31. data/lib/reactor/streaming_upload.rb +22 -0
  32. data/lib/reactor/sudo.rb +11 -0
  33. data/lib/reactor/support/link_matcher.rb +44 -0
  34. data/lib/reactor/tools/workflow_generator.rb +195 -0
  35. data/lib/reactor/validations.rb +55 -0
  36. data/lib/reactor/version.rb +3 -0
  37. data/lib/reactor/workflow.rb +19 -0
  38. data/lib/reactor/workflow/empty.rb +27 -0
  39. data/lib/reactor/workflow/standard.rb +34 -0
  40. metadata +204 -0
@@ -0,0 +1,13 @@
1
+ module Reactor
2
+ module RcIndependent
3
+ module Base
4
+ def really_edited?
5
+ crul_obj.edited?
6
+ end
7
+
8
+ def really_released?
9
+ crul_obj.released?
10
+ end
11
+ end
12
+ end
13
+ end
@@ -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
@@ -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