Workflow ACLs: check workflow creator against regex and for equality (otherwise,...
[openxpki/alech.git] / trunk / perl-modules / core / trunk / OpenXPKI / Server / ACL.pm
blobf9886e0f8d2437589e387f82aae7aa90d2495aad
1 ## OpenXPKI::Server::ACL.pm
2 ##
3 ## Written by Michael Bell 2006
4 ## cleaned up a bit to support multiple PKI realms
5 ## by Alexander Klink 2007
6 ## Copyright (C) 2006 by The OpenXPKI Project
8 package OpenXPKI::Server::ACL;
10 use strict;
11 use warnings;
12 use utf8;
13 use English;
15 use OpenXPKI::Debug;
16 use OpenXPKI::Exception;
17 use OpenXPKI::Server::Context qw( CTX );
19 use Data::Dumper;
21 ## constructor and destructor stuff
23 sub new {
24 my $that = shift;
25 my $class = ref($that) || $that;
27 my $self = {};
29 bless $self, $class;
31 my $keys = shift;
32 ##! 1: "start"
34 return undef if (not $self->__load_config ($keys));
36 ##! 1: "end"
37 return $self;
40 #############################################################################
41 ## load the configuration ##
42 ## (caching support) ##
43 #############################################################################
45 sub __load_config
47 my $self = shift;
48 ##! 1: "start"
50 my $keys = shift;
51 my $cfg_id = $keys->{CONFIG_ID};
53 ## load all PKI realms
55 my $realms = CTX('xml_config')->get_xpath_count(
56 XPATH => 'pki_realm',
57 CONFIG_ID => $cfg_id,
59 for (my $i=0; $i < $realms; $i++)
61 $self->__load_pki_realm ({PKI_REALM => $i, CONFIG_ID => $cfg_id});
64 ##! 1: "leaving function successfully"
65 return 1;
68 sub __load_pki_realm
70 my $self = shift;
71 my $keys = shift;
72 my $realm = $keys->{PKI_REALM};
73 my $cfg_id = $keys->{CONFIG_ID};
75 my $name = CTX('xml_config')->get_xpath (XPATH => ['pki_realm', 'name'],
76 COUNTER => [$realm, 0],
77 CONFIG_ID => $cfg_id,
79 $self->{PKI_REALM}->{$name}->{POS} = $realm;
81 $self->__load_server ({PKI_REALM => $name, CONFIG_ID => $cfg_id});
82 $self->__load_roles ({PKI_REALM => $name, CONFIG_ID => $cfg_id});
83 $self->__load_permissions ({PKI_REALM => $name, CONFIG_ID => $cfg_id});
84 $self->__load_workflow_permissions({
85 PKI_REALM => $name,
86 CONFIG_ID => $cfg_id,
87 });
89 return 1;
92 sub __load_server
94 my $self = shift;
95 my $keys = shift;
96 ##! 1: 'start'
97 my $realm = $keys->{PKI_REALM};
98 my $cfg_id = $keys->{CONFIG_ID};
99 my $pkiid = $self->{PKI_REALM}->{$realm}->{POS};
101 # get the ID of the server that we are on
102 # (for some reason, this ID lives in the database part of the
103 # configuration)
104 my $our_server_id = CTX('xml_config')->get_xpath (
105 XPATH => ['common', 'database', 'server_id'],
106 COUNTER => [0, 0, 0],
107 CONFIG_ID => $cfg_id,
109 my $servers = CTX('xml_config')->get_xpath_count (
110 XPATH => ['pki_realm', 'acl', 'server'],
111 COUNTER => [$pkiid, 0],
112 CONFIG_ID => $cfg_id,
115 for (my $i=0; $i < $servers; $i++) {
116 my $id = CTX('xml_config')->get_xpath (
117 XPATH => ['pki_realm', 'acl', 'server', 'id'],
118 COUNTER => [ $pkiid, 0, $i, 0],
119 CONFIG_ID => $cfg_id,
121 my $name = CTX('xml_config')->get_xpath (
122 XPATH => ['pki_realm', 'acl', 'server', 'name'],
123 COUNTER => [ $pkiid, 0, $i, 0],
124 CONFIG_ID => $cfg_id,
126 if (exists $self->{SERVER}->{$realm}->{$id}) {
127 OpenXPKI::Exception->throw (
128 message => "I18N_OPENXPKI_SERVER_ACL_LOAD_SERVER_DUPLICATE_ID_FOUND",
129 params => {ID => $id,
130 NAME => $name});
132 $self->{SERVER}->{$realm}->{$id} = $name;
133 if ($id == $our_server_id) {
134 $self->{SERVER_NAME} = $name;
137 ##! 16: 'self->{SERVER}: ' . Dumper $self->{SERVER}
138 ##! 1: 'end'
139 return 1;
142 sub __load_roles
144 my $self = shift;
145 my $keys = shift;
146 my $realm = $keys->{PKI_REALM};
147 my $cfg_id = $keys->{CONFIG_ID};
148 my $pkiid = $self->{PKI_REALM}->{$realm}->{POS};
150 my $roles = CTX('xml_config')->get_xpath_count (
151 XPATH => ['pki_realm', 'acl', 'role'],
152 COUNTER => [$pkiid, 0],
153 CONFIG_ID => $cfg_id,
155 for (my $i=0; $i < $roles; $i++)
157 my $role = CTX('xml_config')->get_xpath (
158 XPATH => ['pki_realm', 'acl', 'role'],
159 COUNTER => [ $pkiid, 0, $i],
160 CONFIG_ID => $cfg_id,
162 $self->{PKI_REALM}->{$realm}->{ROLES}->{$role} = 1;
164 ## add empty role for things which have no owner or are owned by the CA
165 $self->{PKI_REALM}->{$realm}->{ROLES}->{""} = 1;
166 return 1;
169 sub __load_permissions
171 my $self = shift;
172 my $keys = shift;
173 my $realm = $keys->{PKI_REALM};
174 my $cfg_id = $keys->{CONFIG_ID};
175 my $pkiid = $self->{PKI_REALM}->{$realm}->{POS};
177 my $perms = CTX('xml_config')->get_xpath_count (
178 XPATH => ['pki_realm', 'acl', 'permission'],
179 COUNTER => [$pkiid, 0],
180 CONFIG_ID => $cfg_id,
182 for (my $i=0; $i < $perms; $i++)
184 my $server = CTX('xml_config')->get_xpath (
185 XPATH => ['pki_realm', 'acl', 'permission', 'server'],
186 COUNTER => [ $pkiid, 0, $i],
187 CONFIG_ID => $cfg_id,
189 my $activity = CTX('xml_config')->get_xpath (
190 XPATH => ['pki_realm', 'acl', 'permission', 'activity'],
191 COUNTER => [ $pkiid, 0, $i],
192 CONFIG_ID => $cfg_id,
194 my $owner = CTX('xml_config')->get_xpath (
195 XPATH => ['pki_realm', 'acl', 'permission', 'affected_role'],
196 COUNTER => [ $pkiid, 0, $i],
197 CONFIG_ID => $cfg_id,
199 my $user = CTX('xml_config')->get_xpath (
200 XPATH => ['pki_realm', 'acl', 'permission', 'auth_role'],
201 COUNTER => [ $pkiid, 0, $i],
202 CONFIG_ID => $cfg_id,
205 my @perms = ();
207 ## evaluate server
208 if ($server ne "*" and
209 $server ne $self->{SERVER_NAME})
211 ## we only need the permissions for this server
212 ## this reduces the propabilities of hash collisions
213 next;
216 ## evaluate owner
217 my @owners = ($owner);
218 @owners = keys %{$self->{PKI_REALM}->{$realm}->{ROLES}}
219 if ($owner eq "*");
221 ## evaluate user
222 my @users = ($user);
223 @users = keys %{$self->{PKI_REALM}->{$realm}->{ROLES}}
224 if ($user eq "*");
226 ## an activity wildcard results in a *
227 ## so we must check always for the activity and *
228 ## before we throw an exception
230 foreach $owner (@owners)
232 foreach $user (@users)
234 $self->{PKI_REALM}->{$realm}->{ACL}->{$owner}->{$user}->{$activity} = 1;
235 ##! 16: "permission: $realm, $owner, $user, $activity"
239 return 1;
242 sub __load_workflow_permissions {
243 my $self = shift;
244 my $keys = shift;
245 ##! 1: 'start'
246 my $realm = $keys->{PKI_REALM};
247 my $cfg_id = $keys->{CONFIG_ID};
248 my $pkiid = $self->{PKI_REALM}->{$realm}->{POS};
250 my $perms = CTX('xml_config')->get_xpath_count(
251 XPATH => [ 'pki_realm', 'acl', 'workflow_permissions' ],
252 COUNTER => [ $pkiid , 0],
253 CONFIG_ID => $cfg_id,
255 ##! 16: 'number of workflow_permissions entries: ' . $perms
256 for (my $i=0; $i < $perms; $i++) {
257 my @base_path = ( 'pki_realm', 'acl', 'workflow_permissions' );
258 my @base_ctr = ( $pkiid , 0 , $i );
259 my $role = CTX('xml_config')->get_xpath(
260 XPATH => [ @base_path, 'role' ],
261 COUNTER => [ @base_ctr , 0 ],
262 CONFIG_ID => $cfg_id,
264 ##! 16: 'role: ' . $role
265 my $servers = CTX('xml_config')->get_xpath_count(
266 XPATH => [ @base_path, 'server' ],
267 COUNTER => [ @base_ctr ],
268 CONFIG_ID => $cfg_id,
270 SERVERS:
271 for (my $ii = 0; $ii < $servers; $ii++) {
272 my $server_name = CTX('xml_config')->get_xpath(
273 XPATH => [ @base_path, 'server', 'name' ],
274 COUNTER => [ @base_ctr , $ii , 0 ],
275 CONFIG_ID => $cfg_id,
277 ##! 16: 'server name: ' . $server_name
278 if ($server_name ne '*' &&
279 $server_name ne $self->{SERVER_NAME}) {
280 ## we only need the permissions for _this_ server
281 next SERVERS;
283 push @base_path, 'server';
284 push @base_ctr , $ii;
285 my $create_entries = CTX('xml_config')->get_xpath_count(
286 XPATH => [ @base_path, 'create' ],
287 COUNTER => [ @base_ctr, ],
288 CONFIG_ID => $cfg_id,
290 ##! 16: 'create entries: ' . Dumper $create_entries
291 for (my $iii = 0; $iii < $create_entries; $iii++) {
292 my $type = CTX('xml_config')->get_xpath(
293 XPATH => [ @base_path, 'create', 'type' ],
294 COUNTER => [ @base_ctr, $iii , 0 ],
295 CONFIG_ID => $cfg_id,
297 ##! 16: 'type: ' . $type
298 $self->{PKI_REALM}->{$realm}->{ACL_WORKFLOW}->{$role}->{CREATE}->{$type} = 1;
300 my $read_entries = CTX('xml_config')->get_xpath_count(
301 XPATH => [ @base_path, 'read' ],
302 COUNTER => [ @base_ctr, ],
303 CONFIG_ID => $cfg_id,
305 ##! 16: 'read entries: ' . Dumper $read_entries
306 for (my $iii = 0; $iii < $read_entries; $iii++) {
307 my $type = CTX('xml_config')->get_xpath(
308 XPATH => [ @base_path, 'read', 'type' ],
309 COUNTER => [ @base_ctr, $iii , 0 ],
310 CONFIG_ID => $cfg_id,
312 ##! 16: 'type: ' . $type
313 my $creator = '$self'; # conservative default, if nothing
314 # is specified in the config, only
315 # allow reading of own workflows
316 eval {
317 $creator = CTX('xml_config')->get_xpath(
318 XPATH => [ @base_path, 'read', 'creator' ],
319 COUNTER => [ @base_ctr, $iii , 0 ],
320 CONFIG_ID => $cfg_id,
323 ##! 16: 'creator: ' . $creator
324 my $context_show = '.*'; # liberal defaults: if nothing is
325 my $context_hide = ''; # specified, allow to read complete
326 # context
327 eval {
328 $context_show = CTX('xml_config')->get_xpath(
329 XPATH => [ @base_path, 'read', 'context_filter', 'show' ],
330 COUNTER => [ @base_ctr, $iii , 0 , 0 ],
331 CONFIG_ID => $cfg_id,
334 eval {
335 $context_hide = CTX('xml_config')->get_xpath(
336 XPATH => [ @base_path, 'read', 'context_filter', 'hide' ],
337 COUNTER => [ @base_ctr, $iii , 0 , 0 ],
338 CONFIG_ID => $cfg_id,
341 ##! 16: 'context_show: ' . $context_show
342 ##! 16: 'context_hide: ' . $context_hide
344 $self->{PKI_REALM}->{$realm}->{ACL_WORKFLOW}->{$role}->{CREATE}->{$type} = 1;
345 $self->{PKI_REALM}->{$realm}->{ACL_WORKFLOW}->{$role}->{READ}->{$type}->{CREATOR} = $creator;
346 $self->{PKI_REALM}->{$realm}->{ACL_WORKFLOW}->{$role}->{READ}->{$type}->{CONTEXT}->{SHOW} = $context_show;
347 $self->{PKI_REALM}->{$realm}->{ACL_WORKFLOW}->{$role}->{READ}->{$type}->{CONTEXT}->{HIDE} = $context_hide;
351 return 1;
353 ########################################################################
354 ## identify the user ##
355 ########################################################################
357 sub authorize_workflow {
358 my $self = shift;
359 my $arg_ref = shift;
360 my $action = $arg_ref->{ACTION};
361 ##! 16: 'action: ' . $action
362 my $realm = CTX('session')->get_pki_realm();
363 ##! 16: 'realm: ' . $realm
364 my $role = CTX('session')->get_role();
365 if ($role eq '') {
366 $role = 'Anonymous';
368 ##! 16: 'role: ' . $role
369 my $user = CTX('session')->get_user();
370 ##! 16: 'user: ' . $user
372 ##! 64: 'ACL_WORKFLOW: ' . Dumper $self->{PKI_REALM}->{$realm}->{ACL_WORKFLOW}
373 if ($action eq 'create') {
374 my $type = $arg_ref->{TYPE};
375 if (! exists $self->{PKI_REALM}->{$realm}->{ACL_WORKFLOW}
376 ->{$role}->{CREATE}->{$type}) {
377 OpenXPKI::Exception->throw(
378 message => 'I18N_OPENXPKI_SERVER_ACL_AUTHORIZE_WORKFLOW_CREATE_PERMISSION_DENIED',
379 params => {
380 'REALM' => $realm,
381 'ROLE' => $role,
382 'WF_TYPE' => $type,
386 return 1;
388 elsif ($action eq 'read') {
389 my $workflow = $arg_ref->{WORKFLOW};
390 my $filter = $arg_ref->{FILTER};
391 my $type = $workflow->type();
392 if (! exists $self->{PKI_REALM}->{$realm}->{ACL_WORKFLOW}->{$role}
393 ->{READ}->{$type}) {
394 OpenXPKI::Exception->throw(
395 message => 'I18N_OPENXPKI_SERVER_ACL_AUTHORIZE_WORKFLOW_READ_PERMISSION_DENIED_NO_ACCESS_TO_TYPE',
398 my $allowed_creator_re = $self->{PKI_REALM}->{$realm}->{ACL_WORKFLOW}->{$role}->{READ}->{$type}->{CREATOR};
399 if ($allowed_creator_re eq '$self') {
400 # this is a meta name, replace by current user name
401 $allowed_creator_re = $user;
403 if ($allowed_creator_re) {
404 ##! 16: 'allowed_creator_re: ' . $allowed_creator_re
405 # if the allowed creator is defined and non-empty, check
406 # it against the workflow creator
407 my $wf_creator = $workflow->context()->param()->{creator};
408 ##! 16: 'wf_creator: ' . $wf_creator
409 if ($wf_creator !~ qr/$allowed_creator_re/ &&
410 $wf_creator ne $allowed_creator_re) {
411 ##! 16: 'workflow creator does not match allowed creator'
412 OpenXPKI::Exception->throw(
413 message => 'I18N_OPENXPKI_SERVER_ACL_AUTHORIZE_WORKFLOW_READ_PERMISSION_DENIED_WORKFLOW_CREATOR_NOT_ACCEPTABLE',
417 if (exists $self->{PKI_REALM}->{$realm}->{ACL_WORKFLOW}->{$role}->{READ}->{$type}->{CONTEXT} && $filter) {
418 # context filtering is defined for this type, so
419 # iterate over the context parameters and check them against
420 # the show/hide configuration values
421 my $show = $self->{PKI_REALM}->{$realm}->{ACL_WORKFLOW}->{$role}->{READ}->{$type}->{CONTEXT}->{SHOW};
422 if (! defined $show) {
423 $show = ''; # paranoid default: do not show anything
425 ##! 16: 'show: ' . $show
426 my $hide = $self->{PKI_REALM}->{$realm}->{ACL_WORKFLOW}->{$role}->{READ}->{$type}->{CONTEXT}->{HIDE};
427 if (! defined $hide) {
428 $hide = ''; # liberal default: do not hide anything
430 my %original_params = %{ $workflow->context()->param() };
431 # clear workflow context so that we can add the ones
432 # that the user is allowed to see to it again ...
433 $workflow->context()->clear_params();
435 ##! 64: 'original_params before filtering: ' . Dumper \%original_params
436 foreach my $key (keys %original_params) {
437 ##! 64: 'key: ' . $key
438 my $add = 1;
439 if ($show) {
440 ##! 16: 'show present, checking against it'
441 if ($key !~ qr/$show/) {
442 ##! 16: 'key ' . $key . ' did not match ' . $show
443 $add = 0;
446 if ($hide) {
447 ##! 16: 'hide present, checking against it'
448 if ($key =~ qr/$hide/) {
449 ##! 16: 'key ' . $key . ' matches ' . $hide
450 $add = 0;
453 if ($add) {
454 ##! 16: 'adding key: ' . $key
455 $workflow->context()->param(
456 $key => $original_params{$key},
460 ##! 64: 'workflow_context after filtering: ' . Dumper $workflow->context->param()
462 return 1;
464 else {
465 OpenXPKI::Exception->throw(
466 message => 'I18N_OPENXPKI_SERVER_ACL_AUTHORIZE_WORKFLOW_UNKNOWN_ACTION',
467 params => {
468 'ACTION' => $action,
472 # this code should be unreachable. In case it is not, throw an exception
473 OpenXPKI::Exception->throw(
474 message => 'I18N_OPENXPKI_SERVER_ACL_AUTHORIZE_WORKFLOW_INTERNAL_ERROR',
478 sub authorize
480 my $self = shift;
481 my $keys = shift;
483 ## we need the following things:
484 ## - PKI realm
485 ## - auth_role
486 ## - affected_role
487 ## - activity
489 my $realm = CTX('session')->get_pki_realm();
490 my $user = CTX('session')->get_role();
491 my $owner = "";
492 $owner = $keys->{AFFECTED_ROLE} if (exists $keys->{AFFECTED_ROLE} and
493 defined $keys->{AFFECTED_ROLE});
494 my $activity = $keys->{ACTIVITY};
496 ##! 99: "user:realm:activity:owner - $user:$realm:$activity:$owner"
498 if (! defined $activity)
500 OpenXPKI::Exception->throw (
501 message => "I18N_OPENXPKI_SERVER_ACL_AUTHORIZE_ACTIVITY_UNDEFINED",
502 params => {PKI_REALM => $realm,
503 AFFECTED_ROLE => $owner,
504 AUTH_ROLE => $user});
507 if ((! exists $self->{PKI_REALM}->{$realm}->{ROLES}->{$owner})
508 && ($activity !~ m{ \A API:: }xms))
509 { # FIXME: we need to figure out a way to find out the affected
510 # role for API calls. For now, it is optional for authorization
511 # of API calls
512 OpenXPKI::Exception->throw (
513 message => "I18N_OPENXPKI_SERVER_ACL_AUTHORIZE_ILLEGAL_AFFECTED_ROLE",
514 params => {PKI_REALM => $realm,
515 ACTIVITY => $activity,
516 AFFECTED_ROLE => $owner,
517 AUTH_ROLE => $user});
520 if (! exists $self->{PKI_REALM}->{$realm}->{ROLES}->{$user})
522 OpenXPKI::Exception->throw (
523 message => "I18N_OPENXPKI_SERVER_ACL_AUTHORIZE_ILLEGAL_AUTH_ROLE",
524 params => {PKI_REALM => $realm,
525 ACTIVITY => $activity,
526 AFFECTED_ROLE => $owner,
527 AUTH_ROLE => $user});
530 my $granted;
531 my $requested_activity = $activity;
533 PERMISSION_CHECK:
534 while ($requested_activity ne '') {
535 if (exists $self->{PKI_REALM}->{$realm}->{ACL}->{$owner}->{$user}->{$requested_activity}) {
536 $granted = 1;
537 last PERMISSION_CHECK;
539 if ($requested_activity eq $activity) {
540 # replace Level1::Level2::activity by Level1::Level2::*
541 if (! ($requested_activity =~ s{ :: [^:]+ \z }{::*}xms)) {
542 OpenXPKI::Exception->throw (
543 message => "I18N_OPENXPKI_SERVER_ACL_AUTHORIZE_MALFORMED_ACTIVITY",
544 params => {
545 PKI_REALM => $realm,
546 ACTIVITY => $activity,
547 AFFECTED_ROLE => $owner,
548 AUTH_ROLE => $user,
550 log => {
551 logger => CTX('log'),
552 priority => 'error',
553 facility => 'auth',
558 elsif ($requested_activity =~ m{ ::\* \z }xms) {
559 # replace Level1::Level2::* by Level1::*
560 $requested_activity =~ s{ [^:]+ ::\* \z }{*}xms;
562 elsif ($requested_activity eq '*') {
563 # last step, replace '*' by ''
564 $requested_activity = '';
566 else {
567 OpenXPKI::Exception->throw (
568 message => "I18N_OPENXPKI_SERVER_ACL_AUTHORIZE_MALFORMED_ACTIVITY",
569 params => {
570 PKI_REALM => $realm,
571 ACTIVITY => $activity,
572 AFFECTED_ROLE => $owner,
573 AUTH_ROLE => $user,
575 log => {
576 logger => CTX('log'),
577 priority => 'error',
578 facility => 'auth',
584 if (! $granted) {
585 ##! 4: 'permission denied'
586 OpenXPKI::Exception->throw (
587 message => "I18N_OPENXPKI_SERVER_ACL_AUTHORIZE_PERMISSION_DENIED",
588 params => {
589 PKI_REALM => $realm,
590 ACTIVITY => $activity,
591 AFFECTED_ROLE => $owner,
592 AUTH_ROLE => $user,
594 log => {
595 logger => CTX('log'),
596 priority => 'info',
597 facility => 'auth',
602 return 1;
605 sub get_roles
607 my $self = shift;
608 return keys %{$self->{PKI_REALM}->{CTX('session')->get_pki_realm()}->{ROLES}};
611 sub get_servers
613 my $self = shift;
614 return $self->{SERVER};
618 __END__
620 =head1 Name
622 OpenXPKI::Server::ACL
624 =head1 Description
626 The ACL module implements the authorization for the OpenXPKI core system.
628 =head1 Functions
630 =head2 new
632 is the constructor of the module.
633 The constructor loads all ACLs of all PKI realms. Every PKI realm must include
634 an ACL section in its configuration. This configuration includes a definition
635 of all servers, all supported roles and all permissions.
637 =head2 authorize
639 is the function which grant the right to execute an activity. The function
640 needs two parameters ACTIVITY and AFFECTED_ROLE. The activity is the activity
641 which is performed by the workflow engine. The affected role is the role of
642 the object which is handled by the activity. If you create a request for
643 a certificate with the role "RA Operator" then the affected role is
644 "RA Operator".
646 The other needed parameters will be automatically determined via the active
647 session. It is not necessary to specify a PKI realm or the role of the logged
648 in user.
650 If the access is granted then function returns a true value. If the access
651 is denied then an exception is thrown.
653 =head2 get_roles
655 returns all available roles for the actual PKI realm.
657 =head2 get_servers
659 returns a hashref that lists all servers by PKI realm