File Coverage

blib/lib/Yukki.pm
Criterion Covered Total %
statement 99 101 98.0
branch 23 26 88.4
condition 6 9 66.6
subroutine 28 28 100.0
pod 7 7 100.0
total 163 171 95.3


line stmt bran cond sub pod time code
1             package Yukki;
2             $Yukki::VERSION = '0.99_01'; # TRIAL
3              
4 3     3   2806549 $Yukki::VERSION = '0.9901';use v5.24;
  3         14  
5 3     3   1021 use utf8;
  3         26  
  3         20  
6 3     3   1623 use Moo;
  3         21717  
  3         18  
7              
8 3     3   4938 use Class::Load;
  3         14130  
  3         132  
9              
10 3     3   1142 use Yukki::Settings;
  3         12  
  3         128  
11 3     3   1204 use Yukki::TextUtil qw( load_file );
  3         10  
  3         15  
12 3     3   678 use Yukki::Types qw( AccessLevel YukkiSettings );
  3         7  
  3         26  
13 3     3   3087 use Yukki::Error qw( http_throw );
  3         13  
  3         14  
14              
15 3     3   1864 use Crypt::SaltedHash;
  3         5337  
  3         90  
16 3     3   18 use List::MoreUtils qw( any );
  3         7  
  3         40  
17 3     3   2116 use Type::Params qw( validate );
  3         7  
  3         23  
18 3     3   604 use Type::Utils;
  3         5  
  3         19  
19 3     3   4133 use Types::Standard qw( Dict HashRef Str Undef slurpy );
  3         7  
  3         23  
20 3     3   2860 use Path::Tiny;
  3         6  
  3         131  
21 3     3   17 use Types::Path::Tiny qw( Path );
  3         6  
  3         24  
22              
23 3     3   1110 use namespace::clean;
  3         6  
  3         20  
24              
25             # ABSTRACT: Yet Uh-nother wiki
26              
27              
28             has config_file => (
29             is => 'ro',
30             isa => Path,
31             required => 1,
32             coerce => 1,
33             lazy => 1,
34             builder => '_build_config_file',
35             );
36              
37             sub _build_config_file {
38 5     5   6818 my $self = shift;
39              
40 5         20 my $cwd_conf = path('.', 'etc', 'yukki.conf');
41 5 100 100     233 if (not $ENV{YUKKI_CONFIG} and -f "$cwd_conf") {
42 1         23 return $cwd_conf;
43             }
44              
45             die("Please make YUKKI_CONFIG point to your configuration file.\n")
46 4 100       66 unless defined $ENV{YUKKI_CONFIG};
47              
48             die("No configuration found at $ENV{YUKKI_CONFIG}. Please set YUKKI_CONFIG to the correct location.\n")
49 3 100       95 unless -f $ENV{YUKKI_CONFIG};
50              
51 2         17 return $ENV{YUKKI_CONFIG};
52             }
53              
54              
55             has settings => (
56             is => 'ro',
57             isa => YukkiSettings,
58             required => 1,
59             coerce => 1,
60             lazy => 1,
61             builder => '_build_settings',
62             );
63              
64             sub _build_settings {
65 2     2   38 my $self = shift;
66 2         34 load_file($self->config_file)
67             }
68              
69              
70 1     1 1 1028 sub view { ... }
71              
72              
73 1     1 1 449 sub controller { ... }
74              
75              
76             sub model {
77 1     1 1 399 my ($self, $name, $params) = @_;
78 1         4 my $class_name = join '::', 'Yukki::Model', $name;
79 1         7 Class::Load::load_class($class_name);
80 1   50     35 return $class_name->new(app => $self, %{ $params // {} });
  1         13  
81             }
82              
83              
84             sub _locate {
85 2     2   8 my ($self, $type, $base, @extra_path) = @_;
86              
87 2         44 my $base_path = $self->settings->$base;
88 2 50       997 if ($base_path !~ m{^/}) {
89 0         0 return path($self->settings->root, $base_path, @extra_path);
90             }
91             else {
92 2         18 return path($base_path, @extra_path);
93             }
94             }
95              
96             sub locate {
97 1     1 1 1368 my ($self, $base, @extra_path) = @_;
98 1         5 $self->_locate(file => $base, @extra_path);
99             }
100              
101              
102             sub locate_dir {
103 1     1 1 3962 my ($self, $base, @extra_path) = @_;
104 1         5 $self->_locate(dir => $base, @extra_path);
105             }
106              
107              
108             sub check_access {
109 42     42 1 1191 my ($self, $opt)
110             = validate(\@_, class_type(__PACKAGE__),
111             slurpy Dict[
112             user => Undef|HashRef,
113             repository => Str,
114             needs => AccessLevel,
115             ]);
116 42         717176 my ($user, $repository, $needs) = @{$opt}{qw( user repository needs )};
  42         8905  
117              
118             # Always grant none
119 42 100       203 return 1 if $needs eq 'none';
120              
121 28         720 my $config = $self->settings->repositories->{$repository};
122              
123 28 50       363 return '' unless $config;
124              
125 28         116 my $read_groups = $config->read_groups;
126 28         81 my $write_groups = $config->write_groups;
127              
128 28         119 my %access_level = (none => 0, read => 1, write => 2);
129             my $has_access = sub {
130 48   50 48   255 $access_level{$_[0] // 'none'} >= $access_level{$needs}
131 28         111 };
132              
133             # Deal with anonymous users first.
134 28 100       96 return 1 if $has_access->($config->anonymous_access_level);
135 22 100       137 return '' unless $user;
136              
137             # Only logged users considered here forward.
138 11   50     20 my @user_groups = @{ $user->{groups} // [] };
  11         51  
139              
140 11         41 for my $level (qw( read write )) {
141 20 100       48 if ($has_access->($level)) {
142              
143 14         325 my $groups = "${level}_groups";
144              
145 14 100       82 return 1 if $config->$groups eq 'ANY';
146              
147 11 100       53 if (ref $config->$groups eq 'ARRAY') {
    50          
148 5         8 my @level_groups = @{ $config->$groups };
  5         19  
149              
150 5         13 for my $level_group (@level_groups) {
151 9 100   9   61 return 1 if any { $_ eq $level_group } @user_groups;
  9         64  
152             }
153             }
154             elsif ($config->$groups ne 'NONE') {
155 0         0 warn "weird value ", $config->$groups,
156             " in $groups config for $repository settings";
157             }
158             }
159             }
160              
161 5         48 return '';
162             }
163              
164              
165             sub hasher {
166 3     3 1 3371 my $self = shift;
167              
168 3         64 return Crypt::SaltedHash->new(algorithm => $self->settings->digest);
169             }
170              
171             with qw( Yukki::Role::App );
172              
173              
174             1;
175              
176             __END__
177              
178             =pod
179              
180             =encoding UTF-8
181              
182             =head1 NAME
183              
184             Yukki - Yet Uh-nother wiki
185              
186             =head1 VERSION
187              
188             version 0.99_01
189              
190             =head1 DESCRIPTION
191              
192             This is intended to be the simplest, stupidest wiki on the planet. It uses git for versioning and it is perfectly safe to clone this repository and push and pull and all that jazz to maintain this wiki in multiple places.
193              
194             For information on getting started see L<Yukki::Manual::Installation>.
195              
196             =head1 WITH ROLES
197              
198             =over
199              
200             =item *
201              
202             L<Yukki::Role::App>
203              
204             =back
205              
206             =head1 ATTRIBUTES
207              
208             =head2 config_file
209              
210             This is the name of the configuraiton file. The application will try to find it in F<etc> within the current working directory first. If not there, it will check the C<YUKKI_CONFIG> environment variable.
211              
212             =head2 settings
213              
214             This is the configuration loaded from the L</config_file>.
215              
216             =head1 METHODS
217              
218             =head2 view
219              
220             my $view = $app->view('Page');
221              
222             Not implemented in this class. See L<Yukki::Web>.
223              
224             =head2 controller
225              
226             my $controller = $app->controller('Page');
227              
228             Not implemented in this class. See L<Yukki::Web>.
229              
230             =head2 model
231              
232             my $model = $app->model('Repository', { repository => 'main' });
233              
234             Returns an instance of the requested model class. The parameters are passed to
235             the instance constructor.
236              
237             =head2 locate
238              
239             my $file = $app->locate('user_path', 'test_user');
240              
241             The first argument is the name of the configuration directive naming the path.
242             It may be followed by one or more path components to be tacked on to the end.
243              
244             Returns a L<Path::Tiny> for the file.
245              
246             =head2 locate_dir
247              
248             my $dir = $app->locate_dir('repository_path', 'main.git');
249              
250             The arguments are identical to L</locate>, but returns a L<Path::Tiny> for
251             the given file.
252              
253             =head2 check_access
254              
255             my $access_is_ok = $app->check_access({
256             user => $user,
257             repository => 'main',
258             needs => 'read',
259             });
260              
261             The C<user> is optional. It should be an object returned from
262             L<Yukki::Model::User>. The C<repository> is required and should be the name of
263             the repository the user is trying to gain access to. The C<needs> is the access
264             level the user needs. It must be an L<Yukki::Types/AccessLevel>.
265              
266             The method returns a true value if access should be granted or false otherwise.
267              
268             =head2 hasher
269              
270             Returns a message digest object that can be used to create a cryptographic hash.
271              
272             =head1 WHY?
273              
274             I wanted a Perl-based, MultiMarkdown-supporting wiki that I could take sermon notes and personal study notes for church and Bible study and such. However, I'm offline at church, so I want to do this from my laptop and sync it up to the master wiki when I get home. That's it.
275              
276             Does it suit your needs? I don't really care, but if I've shared this on the CPAN or the GitHub, then I'm offering it to you in case you might find it useful WITHOUT WARRANTY. If you want it to suit your needs, bug me by email at C<< hanenkamp@cpan.org >> and send me patches.
277              
278             =head1 AUTHOR
279              
280             Andrew Sterling Hanenkamp <hanenkamp@cpan.org>
281              
282             =head1 COPYRIGHT AND LICENSE
283              
284             This software is copyright (c) 2017 by Qubling Software LLC.
285              
286             This is free software; you can redistribute it and/or modify it under
287             the same terms as the Perl 5 programming language system itself.
288              
289             =cut