File Coverage

blib/lib/RapidApp/Template/Access.pm
Criterion Covered Total %
statement 54 94 57.4
branch 12 26 46.1
condition 1 3 33.3
subroutine 15 32 46.8
pod 0 20 0.0
total 82 175 46.8


line stmt bran cond sub pod time code
1             package RapidApp::Template::Access;
2 4     4   2613 use strict;
  4         9  
  4         123  
3 4     4   19 use warnings;
  4         9  
  4         139  
4              
5 4     4   22 use RapidApp::Util qw(:all);
  4         9  
  4         1896  
6              
7 4     4   28 use Moo;
  4         7  
  4         29  
8 4     4   9917 use Types::Standard qw(:all);
  4         9  
  4         29  
9              
10             =pod
11              
12             =head1 DESCRIPTION
13              
14             Base class for access permissions for templates. Designed to work with
15             RapidApp::Template::Controller and RapidApp::Template::Provider
16              
17             Provides 3 access types:
18              
19             =over 4
20              
21             =item * view (compiled)
22             =item * read (raw)
23             =item * write (update)
24              
25             =back
26              
27             =cut
28              
29             # The RapidApp::Template::Controller instance
30             has 'Controller', is => 'ro', required => 1, isa => InstanceOf['RapidApp::Template::Controller'];
31              
32             # $c - localized by RapidApp::Template::Controller specifically for use
33             # in this (or derived) class:
34 2     2 0 19 sub catalyst_context { (shift)->Controller->{_current_context} }
35              
36             # This will be the top-level template name that is being viewed from
37             # the controller, in case different Access rules need to be applied
38             # for templates that are being INCLUDED within another template vs
39             # being viewed/accessed directly.
40 0     0 0 0 sub currently_viewing_template { (shift)->Controller->{_viewing_template} }
41              
42             # Optional internal caching object which may be used by the access class to cache any
43             # data it wants for the life of the Controller request. This is localized at the top
44             # of the view action in the Controller. It is the responsibility of the access class
45             # to keep data organized among the multiple templates which could be processed in a
46             # single request.
47 0 0   0 0 0 sub local_cache { (shift)->{_local_cache} || {} };
48              
49              
50             # Localized in RapidApp::Template::Context::process() to give the Access class
51             # access to the current context object
52 0     0 0 0 sub process_Context { (shift)->{_process_Context} };
53              
54             # -----
55             # Optional *global* settings to toggle access across the board
56              
57             # Normal viewing of compiled/rendered templates. It doesn't make
58             # much sense for this to ever be false.
59             has 'viewable', is => 'ro', isa => Bool, default => sub{1};
60              
61             has 'readable', is => 'ro', lazy => 1, default => sub {
62             my $self = shift;
63            
64             # 'read' is mainly used for updating templates. Default to off
65             # unless an express read/write option has been supplied
66             return (
67             $self->readable_coderef ||
68             $self->readable_regex ||
69             $self->writable_coderef ||
70             $self->writable_regex ||
71             $self->writable
72             ) ? 1 : 0;
73             }, isa => Bool;
74              
75             has 'writable', is => 'ro', lazy => 1, default => sub {
76             my $self = shift;
77              
78             # Defaults to off unless an express writable option is supplied:
79             return (
80             $self->writable_coderef ||
81             $self->writable_regex
82             ) ? 1 : 0;
83             }, isa => Bool;
84              
85             has 'creatable', is => 'ro', lazy => 1, default => sub {
86             my $self = shift;
87              
88             # Defaults to off unless an express writable option is supplied:
89             return (
90             $self->creatable_coderef ||
91             $self->creatable_regex
92             ) ? 1 : 0;
93             }, isa => Bool;
94              
95             has 'deletable', is => 'ro', lazy => 1, default => sub {
96             my $self = shift;
97              
98             # Defaults to off unless an express deletable option is supplied:
99             return (
100             $self->deletable_coderef ||
101             $self->deletable_regex
102             ) ? 1 : 0;
103             }, isa => Bool;
104              
105             # By default, all templates are considered 'admin' templates. Admin templates
106             # are templates which are provided with admin template vars (most notably, [% c %])
107             # when they are rendered. It is very important that only admins have access to
108             # write to admin templates because only admins should be able to access the
109             # Catalyst CONTEXT object $c. It is safe to allow all templates to be admin
110             # templates as long as there is no write access provided (which is the default)
111             # TODO: consider defaulting admin_tpl off when any create/write options are
112             # enabled...
113             has 'admin_tpl', is => 'ro', isa => Bool, default => sub{1};
114              
115              
116             # By default, all templates are considered 'admin' templates... option to specify
117             # via exclude rather than include. For example, to safely provide editable templates
118             # to non-admin or anonymous users you might specify these options together:
119             #
120             # writable_regex => '^wiki',
121             # creatable_regex => '^wiki',
122             # non_admin_tpl_regex => '^wiki',
123             #
124             has 'non_admin_tpl', is => 'ro', lazy => 1, default => sub {
125             my $self = shift;
126              
127             # Defaults to off unless an express non_admin_tpl option is supplied:
128             return (
129             $self->non_admin_tpl_coderef ||
130             $self->non_admin_tpl_regex
131             ) ? 1 : 0;
132             }, isa => Bool;
133             # -----
134              
135              
136             # 'External' templates are those designed to be viewed outside of RapidApp and
137             # are by default publically accessible (i.e. don't require a logged-in session)
138             # These templates cannot be safely viewed within the context of the RapidApp
139             # styles, even when wrapped with 'ra-scoped-reset', and thus must be viewed
140             # in an iframe tab when viewed within the RapidApp/ExtJS interface
141             has 'external_tpl', is => 'ro', lazy => 1, default => sub {
142             my $self = shift;
143              
144             # Defaults to off unless an express external_tpl option is supplied:
145             return (
146             $self->external_tpl_coderef ||
147             $self->external_tpl_regex
148             ) ? 1 : 0;
149             }, isa => Bool;
150              
151              
152             # 'Static' templates are those which should not be processed through TT,
153             # but rendered directly. This content will still be wrapped/post-processed
154             has 'static_tpl', is => 'ro', lazy => 1, default => sub {
155             my $self = shift;
156              
157             # Defaults to off unless an express static_tpl option is supplied:
158             return (
159             $self->static_tpl_coderef ||
160             $self->static_tpl_regex
161             ) ? 1 : 0;
162             }, isa => Bool;
163              
164              
165             # New: default CSS class name to return for every template (called from template_css_class())
166             # unless this is set to 'undef', this class name will be added to the div wrapper when
167             # rendering the template (along with 'ra-template'). This attr will have no effect if
168             # the 'template_css_class' method is overridden.
169             # For backward compatability, this is currently set to 'ra-doc' for out-of-the-box nice styles
170             # without needing to set a config, however, a default of undef might make more sense.
171             # currently, apps will need to manually set this to undef to avoid the default styles, which
172             # is needed when there are better/custom styles.
173             has 'default_template_css_class', is => 'ro', isa => Maybe[Str], default => sub { 'ra-doc' };
174              
175             # Optional CodeRef interfaces:
176             has 'get_template_vars_coderef', is => 'ro', isa => Maybe[CodeRef], default => sub {undef};
177             has 'get_template_format_coderef', is => 'ro', isa => Maybe[CodeRef], default => sub {undef};
178              
179             # common handling for specific bool 'permissions':
180             has 'viewable_coderef', is => 'ro', isa => Maybe[CodeRef], default => sub {undef};
181             has 'readable_coderef', is => 'ro', isa => Maybe[CodeRef], default => sub {undef};
182             has 'writable_coderef', is => 'ro', isa => Maybe[CodeRef], default => sub {undef};
183             has 'creatable_coderef', is => 'ro', isa => Maybe[CodeRef], default => sub {undef};
184             has 'deletable_coderef', is => 'ro', isa => Maybe[CodeRef], default => sub {undef};
185             has 'admin_tpl_coderef', is => 'ro', isa => Maybe[CodeRef], default => sub {undef};
186             has 'non_admin_tpl_coderef', is => 'ro', isa => Maybe[CodeRef], default => sub {undef};
187             has 'external_tpl_coderef', is => 'ro', isa => Maybe[CodeRef], default => sub {undef};
188             has 'static_tpl_coderef', is => 'ro', isa => Maybe[CodeRef], default => sub {undef};
189              
190             # Optional Regex interfaces:
191             has 'viewable_regex', is => 'ro', isa => Maybe[Str], default => sub {undef};
192             has 'readable_regex', is => 'ro', isa => Maybe[Str], default => sub {undef};
193             has 'writable_regex', is => 'ro', isa => Maybe[Str], default => sub {undef};
194             has 'creatable_regex', is => 'ro', isa => Maybe[Str], default => sub {undef};
195             has 'deletable_regex', is => 'ro', isa => Maybe[Str], default => sub {undef};
196             has 'admin_tpl_regex', is => 'ro', isa => Maybe[Str], default => sub {undef};
197             has 'non_admin_tpl_regex', is => 'ro', isa => Maybe[Str], default => sub {undef};
198             has 'external_tpl_regex', is => 'ro', isa => Maybe[Str], default => sub {undef};
199             has 'static_tpl_regex', is => 'ro', isa => Maybe[Str], default => sub {undef};
200              
201              
202             # Compiled regexes:
203             has '_viewable_regexp', is => 'ro', lazy => 1, default => sub {
204             my $self = shift;
205             my $str = $self->viewable_regex or return undef;
206             return qr/$str/;
207             }, isa => Maybe[RegexpRef];
208              
209             has '_readable_regexp', is => 'ro', lazy => 1, default => sub {
210             my $self = shift;
211             my $str = $self->readable_regex or return undef;
212             return qr/$str/;
213             }, isa => Maybe[RegexpRef];
214              
215             has '_writable_regexp', is => 'ro', lazy => 1, default => sub {
216             my $self = shift;
217             my $str = $self->writable_regex or return undef;
218             return qr/$str/;
219             }, isa => Maybe[RegexpRef];
220              
221             has '_creatable_regexp', is => 'ro', lazy => 1, default => sub {
222             my $self = shift;
223             my $str = $self->creatable_regex or return undef;
224             return qr/$str/;
225             }, isa => Maybe[RegexpRef];
226              
227             has '_deletable_regexp', is => 'ro', lazy => 1, default => sub {
228             my $self = shift;
229             my $str = $self->deletable_regex or return undef;
230             return qr/$str/;
231             }, isa => Maybe[RegexpRef];
232              
233             has '_admin_tpl_regexp', is => 'ro', lazy => 1, default => sub {
234             my $self = shift;
235             my $str = $self->admin_tpl_regex or return undef;
236             return qr/$str/;
237             }, isa => Maybe[RegexpRef];
238              
239             has '_non_admin_tpl_regexp', is => 'ro', lazy => 1, default => sub {
240             my $self = shift;
241             my $str = $self->non_admin_tpl_regex or return undef;
242             return qr/$str/;
243             }, isa => Maybe[RegexpRef];
244              
245             has '_external_tpl_regexp', is => 'ro', lazy => 1, default => sub {
246             my $self = shift;
247             my $str = $self->external_tpl_regex or return undef;
248             return qr/$str/;
249             }, isa => Maybe[RegexpRef];
250              
251             has '_static_tpl_regexp', is => 'ro', lazy => 1, default => sub {
252             my $self = shift;
253             my $str = $self->static_tpl_regex or return undef;
254             return qr/$str/;
255             }, isa => Maybe[RegexpRef];
256              
257             # Class/method interfaces to override in derived class when additional
258             # calculations are needed beyond the simple, built-in options (i.e.
259             # user/role based checks. Note: get '$c' via $self->catalyst_context :
260              
261             # NOTE: if non-admins are granted access to write templates in a production
262             # system a custom get_template_vars should be supplied because the default
263             # provides full access to the Catalyst Context object ($c) - or, the supplied
264             # 'admin_tpl' or 'non_admin_tpl' permissions need to be configured accordingly
265             sub get_template_vars {
266 1     1 0 5 my ($self,@args) = @_;
267            
268             # Note that the default get_template_vars() doesn't care about the
269             # template (all of them get the same vars) but the API accpets the
270             # template as an arg so derived classes can apply template-specific
271             # rules/permissions to the vars supplied to the template
272 1         6 my $template = join('/',@args);
273            
274             # defer to coderef, if supplied:
275 1 50       8 return $self->get_template_vars_coderef->($self,$template)
276             if ($self->get_template_vars_coderef);
277            
278 1 50       5 return $self->template_admin_tpl($template)
279             ? $self->_get_admin_template_vars($template)
280             : $self->_get_default_template_vars($template);
281             }
282              
283             sub _get_default_template_vars {
284 1     1   3 my ($self, $template) = @_;
285 1         8 my $c = $self->catalyst_context;
286 1         19 my $Provider = $self->Controller->get_Provider;
287 1         7 my $vars = {};
288             $vars = {
289             # TODO: figure out what other variables would be safe to provide to
290             # non-admin templates
291             template_name => $template,
292             rapidapp_version => $RapidApp::VERSION,
293            
294 0     0   0 list_templates => sub { $Provider->list_templates(@_) },
295            
296             # Return the url for the supplied template,
297             # relative to the current request action:
298             template_url => sub {
299 0     0   0 my $tpl = shift;
300 0         0 return join('','/',$c->req->action,"/$tpl");
301             },
302            
303             template_link => sub {
304 0     0   0 my $tpl = shift;
305 0         0 my $url = $vars->{template_url}->($tpl);
306 0         0 return join('','<a href="#!',$url,'">',$tpl,'</a>');
307             },
308            
309             local_uri => sub {
310 0 0   0   0 return undef unless ($c);
311 0         0 my $uri_str = $c->req->uri->rel($c->req->base)->as_string;
312 0 0       0 $uri_str = '' if ($uri_str eq './');
313 0         0 return "/$uri_str";
314             }
315            
316 1         22 };
317            
318 1         7 return $vars;
319             }
320              
321             # Admin templates get access to the context object. Only admin users
322             # should be able to write admin templates for obvious reasons
323             sub _get_admin_template_vars {
324 1     1   3 my $self = shift;
325             return {
326 1         2 %{ $self->_get_default_template_vars(@_) },
  1         4  
327             c => $self->catalyst_context,
328             };
329             }
330              
331              
332             # Returns a hashref of optional overrides for the Ext panel config
333             # returned when rendering the template via AutoPanel (i.e. tab). This
334             # is useful for places where you need to set template-specific options,
335             # such as setting 'autopanel_refresh_interval' if you want one
336             # specific template to auto refresh. This has no effect when rendered
337             # any place other than within AutoPanels in the JS client via the
338             # Template::Controller.
339 0     0 0 0 sub template_autopanel_cnf { {} }
340              
341              
342             # Simple bool permission methods:
343              
344             sub template_viewable {
345 0     0 0 0 my ($self,@args) = @_;
346 0         0 my $template = join('/',@args);
347            
348 0         0 return $self->_access_test($template,'viewable',1);
349             }
350              
351             sub template_readable {
352 0     0 0 0 my ($self,@args) = @_;
353 0         0 my $template = join('/',@args);
354            
355 0         0 return $self->_access_test($template,'readable',1);
356             }
357              
358             sub template_writable {
359 0     0 0 0 my ($self,@args) = @_;
360 0         0 my $template = join('/',@args);
361            
362 0         0 return $self->_access_test($template,'writable',1);
363             }
364              
365             sub template_creatable {
366 0     0 0 0 my ($self,@args) = @_;
367 0         0 my $template = join('/',@args);
368            
369 0         0 return $self->_access_test($template,'creatable',1);
370             }
371              
372             sub template_deletable {
373 0     0 0 0 my ($self,@args) = @_;
374 0         0 my $template = join('/',@args);
375            
376 0         0 return $self->_access_test($template,'deletable',1);
377             }
378              
379             sub template_admin_tpl {
380 1     1 0 4 my ($self,@args) = @_;
381 1         4 my $template = join('/',@args);
382            
383 1 50       6 return $self->template_non_admin_tpl($template)
384             ? 0 : $self->_access_test($template,'admin_tpl',1);
385             }
386              
387             sub template_non_admin_tpl {
388 1     1 0 3 my ($self,@args) = @_;
389 1         4 my $template = join('/',@args);
390            
391 1         5 return $self->_access_test($template,'non_admin_tpl',1);
392             }
393              
394             sub template_external_tpl {
395 0     0 0 0 my ($self,@args) = @_;
396 0         0 my $template = join('/',@args);
397            
398 0         0 return $self->_access_test($template,'external_tpl',1);
399             }
400              
401             sub template_static_tpl {
402 2     2 0 7 my ($self,@args) = @_;
403 2         7 my $template = join('/',@args);
404            
405 2         9 return $self->_access_test($template,'static_tpl',1);
406             }
407              
408             sub _access_test {
409 4     4   53 my ($self,$template,$perm,$default) = @_;
410            
411 4         15 my ($global,$regex,$code) = (
412             $perm,
413             '_' . $perm . '_regexp',
414             $perm . '_coderef',
415             );
416            
417             #check global setting
418 4 100       75 return 0 unless ($self->$perm);
419            
420             # Check regex, if supplied:
421 1 50 33     31 return 0 if (
422             $self->$regex &&
423             ! ($template =~ $self->$regex)
424             );
425            
426             # defer to coderef, if supplied:
427 1 50       33 return $self->$code->($self,$template)
428             if ($self->$code);
429            
430             # Default:
431 1         10 return $default;
432             }
433              
434              
435             # New: returns a format string to be included in the template metadata
436             sub get_template_format {
437 4     4 0 7 my ($self,@args) = @_;
438 4         11 my $template = join('/',@args);
439            
440             # defer to coderef, if supplied:
441 4 50       12 return $self->get_template_format_coderef->($self,$template)
442             if ($self->get_template_format_coderef);
443            
444             # By default we treat any *.md templates as markdown
445 4 50       13 return 'markdown' if ($template =~ /\.md$/i);
446            
447             # html-snippet can be safely edited by the HtmlEditor
448 4 100       26 return 'html-snippet' if ($template =~ /\.tt$/i);
449            
450             # TODO: add other formats here ...
451            
452             # The default format should always be 'html':
453 2         5 return 'html';
454             }
455              
456             # Returns an optional css class name associated with a given template
457             # that should be added to teh div wrapper when rendering the template
458             sub template_css_class {
459 0     0 0 0 my ($self,@args) = @_;
460 0         0 my $template = join('/',@args);
461 0         0 return $self->default_template_css_class;
462             }
463              
464              
465             # New: must return undef or a class name to use for "post-processing" of the
466             # supplied template name. Returned class should be a valid perl package name
467             # which has a 'process' method which may be called as a class method, accepting
468             # a blob of text as ScalarRef and returning the post-processed content.
469             # Will receive the RapidApp::Template::Context object as second argument.
470             sub template_post_processor_class {
471 2     2 0 5 my ($self,@args) = @_;
472 2         5 my $template = join('/',@args);
473            
474 2         4 my $format = $self->get_template_format($template);
475            
476             return
477 2 50       11 $format eq 'markdown' ? 'RapidApp::Template::Postprocessor::Markdown' :
478             # TODO: add additional postprocessors ...
479            
480             undef
481             }
482              
483              
484             # Optionally return headers (i.e. Content-Type, etc) associated with a given
485             # external template.
486             sub get_external_tpl_headers {
487 0     0 0   my ($self,@args) = @_;
488 0           my $template = join('/',@args);
489             return undef
490 0           }
491              
492              
493             # This is called by the controller at the top of the view request, and if it
494             # returns a value, that will be used as the psgi response, bypassing all other
495             # logic/rules:
496             sub template_psgi_response {
497 0     0 0   my ($self,$template,$c) = @_;
498             return undef
499 0           }
500              
501              
502              
503             1;