File Coverage

blib/lib/Yukki.pm
Criterion Covered Total %
statement 105 107 98.1
branch 24 28 85.7
condition 9 16 56.2
subroutine 28 28 100.0
pod 7 7 100.0
total 173 186 93.0


line stmt bran cond sub pod time code
1             package Yukki;
2             $Yukki::VERSION = '0.991_002'; # TRIAL
3              
4 3     3   821994 $Yukki::VERSION = '0.991002';use v5.24;
  3         14  
5 3     3   25 use utf8;
  3         11  
  3         42  
6 3     3   1424 use Moo;
  3         26227  
  3         20  
7              
8 3     3   5336 use Class::Load;
  3         14384  
  3         151  
9              
10 3     3   1009 use Yukki::Settings;
  3         11  
  3         240  
11 3     3   1532 use Yukki::TextUtil qw( load_file );
  3         11  
  3         13  
12 3     3   675 use Yukki::Types qw( AccessLevel YukkiSettings );
  3         6  
  3         59  
13 3     3   3244 use Yukki::Error qw( http_throw );
  3         14  
  3         20  
14              
15 3     3   2595 use Crypt::SaltedHash;
  3         6794  
  3         152  
16 3     3   36 use List::Util qw( any );
  3         8  
  3         299  
17 3     3   23 use Type::Params qw( validate );
  3         8  
  3         84  
18 3     3   1043 use Type::Utils;
  3         8  
  3         26  
19 3     3   4754 use Types::Standard qw( Dict HashRef Str Maybe slurpy Optional );
  3         8  
  3         68  
20 3     3   4022 use Path::Tiny;
  3         9  
  3         221  
21 3     3   27 use Types::Path::Tiny qw( Path );
  3         9  
  3         38  
22              
23 3     3   1388 use namespace::clean;
  3         8  
  3         41  
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   8188 my $self = shift;
39              
40 5         29 my $cwd_conf = path('.', 'etc', 'yukki.conf');
41 5 100 100     299 if (not $ENV{YUKKI_CONFIG} and -f "$cwd_conf") {
42 1         34 return $cwd_conf;
43             }
44              
45             die("Please make YUKKI_CONFIG point to your configuration file.\n")
46 4 100       112 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       118 unless -f $ENV{YUKKI_CONFIG};
50              
51 2         24 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   63 my $self = shift;
66 2         47 load_file($self->config_file)
67             }
68              
69              
70 1     1 1 1058 sub view { ... }
71              
72              
73 1     1 1 424 sub controller { ... }
74              
75              
76             sub model {
77 1     1 1 366 my ($self, $name, $params) = @_;
78 1         6 my $class_name = join '::', 'Yukki::Model', $name;
79 1         10 Class::Load::load_class($class_name);
80 1   50     50 return $class_name->new(app => $self, %{ $params // {} });
  1         16  
81             }
82              
83              
84             sub _locate {
85 2     2   8 my ($self, $type, $base, @extra_path) = @_;
86              
87 2         75 my $base_path = $self->settings->$base;
88 2         1393 my $root_path;
89              
90 2 50       72 if ($base_path !~ m{^/}) {
91 0         0 $root_path = path($self->settings->root, $base_path);
92             }
93             else {
94 2         20 $root_path = path($base_path);
95             }
96              
97 2         34 my $located_path = $root_path->child(@extra_path);
98              
99             # Small safety mechanism
100 2 50       70 die "attempted to lookup an illegal $base path: ", join('/', @extra_path)
101             unless $root_path->subsumes($located_path);
102              
103 2         244 return $located_path;
104             }
105              
106             sub locate {
107 1     1 1 928 my ($self, $base, @extra_path) = @_;
108 1         4 $self->_locate(file => $base, @extra_path);
109             }
110              
111              
112             sub locate_dir {
113 1     1 1 5825 my ($self, $base, @extra_path) = @_;
114 1         6 $self->_locate(dir => $base, @extra_path);
115             }
116              
117              
118             sub check_access {
119 42     42 1 392 my ($self, $opt)
120             = validate(\@_, class_type(__PACKAGE__),
121             slurpy Dict[
122             user => Maybe[class_type('Yukki::User')],
123             special => Optional[Str],
124             repository => Optional[Str],
125             needs => AccessLevel,
126             ]
127             );
128             my ($user, $repository, $special, $needs)
129 42         553028 = @{$opt}{qw( user repository special needs )};
  42         17353  
130              
131 42   50     150 $repository //= '-';
132 42   50     304 $special //= '-';
133              
134             # Always grant none
135 42 100       282 return 1 if $needs eq 'none';
136              
137             my $config = $self->settings->repositories->{$repository}
138 28   33     1083 // $self->settings->special_privileges->{$special};
139              
140 28 50       538 return '' unless $config;
141              
142 28         165 my $read_groups = $config->read_groups;
143 28         107 my $write_groups = $config->write_groups;
144              
145 28         432 my %access_level = (none => 0, read => 1, write => 2);
146             my $has_access = sub {
147 48   50 48   309 $access_level{$_[0] // 'none'} >= $access_level{$needs}
148 28         162 };
149              
150             # Deal with anonymous users first.
151 28 100       152 return 1 if $has_access->($config->anonymous_access_level);
152 22 100       231 return '' unless $user;
153              
154             # Only logged users considered here forward.
155 11   50     26 my @user_groups = @{ $user->{groups} // [] };
  11         62  
156              
157 11         38 for my $level (qw( read write )) {
158 20 100       41 if ($has_access->($level)) {
159              
160 14         42 my $groups = "${level}_groups";
161              
162 14 100       117 return 1 if $config->$groups eq 'ANY';
163              
164 11 100       60 if (ref $config->$groups eq 'ARRAY') {
    50          
165 5         14 my @level_groups = @{ $config->$groups };
  5         23  
166              
167 5         14 for my $level_group (@level_groups) {
168 9 100   9   40 return 1 if any { $_ eq $level_group } @user_groups;
  9         78  
169             }
170             }
171             elsif ($config->$groups ne 'NONE') {
172 0         0 warn "weird value ", $config->$groups,
173             " in $groups config for $repository settings";
174             }
175             }
176             }
177              
178 5         67 return '';
179             }
180              
181              
182             sub hasher {
183 3     3 1 3153239 my $self = shift;
184              
185 3         81 return Crypt::SaltedHash->new(algorithm => $self->settings->digest);
186             }
187              
188             with qw( Yukki::Role::App );
189              
190              
191             1;
192              
193             __END__
194              
195             =pod
196              
197             =encoding UTF-8
198              
199             =head1 NAME
200              
201             Yukki - Yet Uh-nother wiki
202              
203             =head1 VERSION
204              
205             version 0.991_002
206              
207             =head1 DESCRIPTION
208              
209             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.
210              
211             For information on getting started see L<Yukki::Manual::Installation>.
212              
213             =head1 WITH ROLES
214              
215             =over
216              
217             =item *
218              
219             L<Yukki::Role::App>
220              
221             =back
222              
223             =head1 ATTRIBUTES
224              
225             =head2 config_file
226              
227             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.
228              
229             =head2 settings
230              
231             This is the configuration loaded from the L</config_file>.
232              
233             =head1 METHODS
234              
235             =head2 view
236              
237             my $view = $app->view('Page');
238              
239             Not implemented in this class. See L<Yukki::Web>.
240              
241             =head2 controller
242              
243             my $controller = $app->controller('Page');
244              
245             Not implemented in this class. See L<Yukki::Web>.
246              
247             =head2 model
248              
249             my $model = $app->model('Repository', { repository => 'main' });
250              
251             Returns an instance of the requested model class. The parameters are passed to
252             the instance constructor.
253              
254             =head2 locate
255              
256             my $file = $app->locate('user_path', 'test_user');
257              
258             The first argument is the name of the configuration directive naming the path.
259             It may be followed by one or more path components to be tacked on to the end.
260              
261             Returns a L<Path::Tiny> for the file.
262              
263             =head2 locate_dir
264              
265             my $dir = $app->locate_dir('repository_path', 'main.git');
266              
267             The arguments are identical to L</locate>, but returns a L<Path::Tiny> for
268             the given file.
269              
270             =head2 check_access
271              
272             my $access_is_ok = $app->check_access({
273             user => $user,
274             repository => 'main',
275             needs => 'read',
276             });
277              
278             The C<user> is optional. It should be an object returned from
279             L<Yukki::Model::User>. The C<repository> is required and should be the name of
280             the repository the user is trying to gain access to. The C<needs> is the access
281             level the user needs. It must be an L<Yukki::Types/AccessLevel>.
282              
283             The method returns a true value if access should be granted or false otherwise.
284              
285             =head2 hasher
286              
287             Returns a message digest object that can be used to create a cryptographic hash.
288              
289             =head1 WHY?
290              
291             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.
292              
293             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.
294              
295             =head1 AUTHOR
296              
297             Andrew Sterling Hanenkamp <hanenkamp@cpan.org>
298              
299             =head1 COPYRIGHT AND LICENSE
300              
301             This software is copyright (c) 2017 by Qubling Software LLC.
302              
303             This is free software; you can redistribute it and/or modify it under
304             the same terms as the Perl 5 programming language system itself.
305              
306             =cut