1 ## OpenXPKI::Server::ACL.pm
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
;
16 use OpenXPKI
::Exception
;
17 use OpenXPKI
::Server
::Context
qw( CTX );
21 ## constructor and destructor stuff
25 my $class = ref($that) || $that;
34 return undef if (not $self->__load_config ($keys));
40 #############################################################################
41 ## load the configuration ##
42 ## (caching support) ##
43 #############################################################################
51 my $cfg_id = $keys->{CONFIG_ID
};
53 ## load all PKI realms
55 my $realms = CTX
('xml_config')->get_xpath_count(
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"
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],
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({
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
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,
132 $self->{SERVER
}->{$realm}->{$id} = $name;
133 if ($id == $our_server_id) {
134 $self->{SERVER_NAME
} = $name;
137 ##! 16: 'self->{SERVER}: ' . Dumper $self->{SERVER}
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;
169 sub __load_permissions
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,
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
217 my @owners = ($owner);
218 @owners = keys %{$self->{PKI_REALM
}->{$realm}->{ROLES
}}
223 @users = keys %{$self->{PKI_REALM
}->{$realm}->{ROLES
}}
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"
242 sub __load_workflow_permissions
{
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,
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
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
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
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,
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;
353 ########################################################################
354 ## identify the user ##
355 ########################################################################
357 sub authorize_workflow
{
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();
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',
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}
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
440 ##! 16: 'show present, checking against it'
441 if ($key !~ qr/$show/) {
442 ##! 16: 'key ' . $key . ' did not match ' . $show
447 ##! 16: 'hide present, checking against it'
448 if ($key =~ qr/$hide/) {
449 ##! 16: 'key ' . $key . ' matches ' . $hide
454 ##! 16: 'adding key: ' . $key
455 $workflow->context()->param(
456 $key => $original_params{$key},
460 ##! 64: 'workflow_context after filtering: ' . Dumper $workflow->context->param()
465 OpenXPKI
::Exception
->throw(
466 message
=> 'I18N_OPENXPKI_SERVER_ACL_AUTHORIZE_WORKFLOW_UNKNOWN_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',
483 ## we need the following things:
489 my $realm = CTX
('session')->get_pki_realm();
490 my $user = CTX
('session')->get_role();
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
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});
531 my $requested_activity = $activity;
534 while ($requested_activity ne '') {
535 if (exists $self->{PKI_REALM
}->{$realm}->{ACL
}->{$owner}->{$user}->{$requested_activity}) {
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",
546 ACTIVITY
=> $activity,
547 AFFECTED_ROLE
=> $owner,
551 logger
=> CTX
('log'),
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 = '';
567 OpenXPKI
::Exception
->throw (
568 message
=> "I18N_OPENXPKI_SERVER_ACL_AUTHORIZE_MALFORMED_ACTIVITY",
571 ACTIVITY
=> $activity,
572 AFFECTED_ROLE
=> $owner,
576 logger
=> CTX
('log'),
585 ##! 4: 'permission denied'
586 OpenXPKI
::Exception
->throw (
587 message
=> "I18N_OPENXPKI_SERVER_ACL_AUTHORIZE_PERMISSION_DENIED",
590 ACTIVITY
=> $activity,
591 AFFECTED_ROLE
=> $owner,
595 logger
=> CTX
('log'),
608 return keys %{$self->{PKI_REALM
}->{CTX
('session')->get_pki_realm()}->{ROLES
}};
614 return $self->{SERVER
};
622 OpenXPKI::Server::ACL
626 The ACL module implements the authorization for the OpenXPKI core system.
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.
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
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
650 If the access is granted then function returns a true value. If the access
651 is denied then an exception is thrown.
655 returns all available roles for the actual PKI realm.
659 returns a hashref that lists all servers by PKI realm