#!/usr/bin/env tclsh

#
# see https://www.openldap.org/doc/admin24/access-control.html
# and the manual page slapd.access(5) for documentation
#

#
# GLOBAL VARIABLES
#

set ldapsuffix     [lindex $argv 0]
set kerberos_realm [lindex $argv 1]
set samba_domain   [lindex $argv 2]

if {[llength $argv] != 3 || $ldapsuffix eq "" || $kerberos_realm eq ""
      || $samba_domain eq ""} {
  puts stderr "usage: puavo-print-acl ldapsuffix kerberos_realm samba_domain"
  exit 1
}

set rules [list]

#
# SUBROUTINES
#

proc attrs {args} {
  format {attrs="%s"} [join $args ","]
}

proc devicetype_set {devtype} {
  ldapset [format {user/puavoDeviceType & [%s]} $devtype]
}

proc dn {{_scope ""} {_branch ""}} {
  global ldapsuffix
  set branch [expr { $_branch ne "" ? "${_branch}," : ""}]
  set scope  [expr { $_scope  ne "" ? ".${_scope}"  : ""}]
  format {dn%s="%s%s"} $scope $branch $ldapsuffix
}

proc ldapset {setspec} {
  format {set="%s"} $setspec
}

proc person_affiliation {affliation} {
  format {user/puavoEduPersonAffiliation & [%s]} $affliation
}

proc puavo_dn {name} {
  format {dn.exact="uid=%s,o=puavo"} $name
}

proc rule {rulelines} {
  global rules
  set uncommented_rulelines [regsub -all -line {#.*$} $rulelines ""]
  set subst_rulelines [uplevel 1 [list subst $uncommented_rulelines]]
  set rule_elems [split $subst_rulelines]
  set nonempty_rule_elems [
    lmap _ $rule_elems { expr { $_ ne "" ? $_ : [continue] } }
  ]
  lappend rules [join $nonempty_rule_elems " "]
}

proc systemgroup {groupname} {
  global ldapsuffix
  format {group/puavoSystemGroup/member="cn=%s,ou=System Groups,%s"} \
         $groupname $ldapsuffix
}

proc admins_with_permission {permission} {
  ldapset [
    format {([admin,%s])
              & (user/puavoEduPersonAffiliation
                  + [,] + user/puavoAdminPermissions)} \
           $permission
  ]
}

proc this_school_teachers_with_permission {permission} {
  ldapset [
    format {([teacher,%s,] + this/puavoSchool)
              & (user/puavoEduPersonAffiliation
                  + [,] + user/puavoTeacherPermissions
                  + [,] + user/puavoSchool)} \
           $permission
  ]
}

#
# some constants
#

set Desktops        "ou=Desktops"
set Devices         "ou=Devices,ou=Hosts"
set Domain_Admins   "cn=Domain Admins,ou=Groups"
set Domain_Users    "cn=Domain Users,ou=Groups"
set Files           "ou=Files,ou=Desktops"
set Groups          "ou=Groups"
set Hosts           "ou=Hosts"
set People          "ou=People"
set Printers        "ou=Printers"
set Servers         "ou=Servers,ou=Hosts"
set System_Accounts "ou=System Accounts"
set System_Groups   "ou=System Groups"

set org_owners   [ldapset [format {[%s]/owner & user} $ldapsuffix]]
set schooladmins [ldapset "this & user/puavoAdminOfSchool"]
set admins       [ldapset [person_affiliation admin]]
set teachers     [ldapset [person_affiliation teacher]]

# "self" but only if self is an admin
set selfadmins [
  ldapset {(user/puavoEduPersonAffiliation + [,] + this) & ([admin,] + user)}
]

# "self" but only if self is a teacher
set selfteachers [
  ldapset {(user/puavoEduPersonAffiliation + [,] + this) & ([teacher,] + user)}
]

# this means: admins of the same school as
# $this_thing (person, device, server) is in
set this_school_admins [ldapset "this/puavoSchool & user/puavoAdminOfSchool"]

# this means: target/what and actor/who must be in the same school
# and actor/who must also be a teacher
set this_school_teachers [ldapset \
  {([teacher,] + this/puavoSchool)
     & (user/puavoEduPersonAffiliation + [,] + user/puavoSchool)}]

#
# RULES
#

# special wide rules

rule {
  # Give slave ldap servers read access to everything
  # (in the same organisation of course) so that ldap replication works.
  [dn subtree]
      by [puavo_dn slave] read
      by *                none break
}

#
# We list all access rules for [dn children $Servers] first, because servers
# need full read access to the database for ldap replication to work.
# Now follows the write access rules to specific attributes.
#

# Samba-related rules

# Servers need write to samba-related stuff for Samba to work correctly.

rule {
  [dn exact ou=Samba,ou=Hosts]
    [attrs children]
      by [dn children $Servers] write
}

rule {
  [dn children ou=Samba,ou=Hosts]
      by [dn children $Servers] write
}

rule {
  # This is needed for all who need to create users,
  # plus Samba in case of Windows compatibility.
  [dn exact sambaDomainName=${samba_domain}]
    [attrs sambaNextRid]
      by [dn children $Servers] write
      by *                      none break
}

rule {
  # This is probably needed by Samba (?).
  [dn exact ou=Idmap]
      by [dn children $Servers] write
}

rule {
  # Changes to "sambaNTPassword", "sambaPwdLastSet", "sambaPwdMustChange"
  # and "sambaPwdCanChange" happen regardless of the ACLs, through
  # smbkrb5pwd (and is thus controlled by ACLs to "userPassword").
  [dn children $People]
    [attrs sambaNTPassword]
      by [dn children $Servers] write   # XXX why is this needed?

      # be explicit that nobody should have permissions to this
      by * none
}

# Samba-related rule under ou=People
rule {
  [dn children $People]
    [attrs sambaAcctFlags]
      by [dn children $Servers] write
      by *                      none break
}

rule {
  # Allow servers to write "puavoDeviceHWInfo" to all netbooting devices.
  # This is because they can act as proxies for fatclients.
  [dn children $Devices]
    # This is only needed by puavoNetbootDevices.
    filter="(objectClass=puavoNetbootDevice)"
    [attrs puavoDeviceHWInfo puavoDeviceMonitorsXML]
      by [dn children $Servers] write
      by *                      none break
}

rule {
  [dn exact $Printers]
    [attrs children]
      by [dn children $Servers] write   # for puavo-sync-printers
      by *                      none break
}

rule {
  [dn children $Printers]
      by [dn children $Servers] write   # for puavo-sync-printers
      by *                      none break
}

rule {
  [dn children $Servers]
    [attrs puavoDeviceAvailableImage puavoDeviceCurrentImage puavoDeviceHWInfo]
      by self write
      by *    none break
}

rule {
  # Give servers read access to everything (in the same organisation
  # of course) so that ldap replication works.
  [dn subtree]
      by [dn children $Servers] read
      by *                      none break
}

# set some early rules for userPassword first so that later rules
# do not accidentally grant rights to it
# "=axz" means "add authenticate delete", which excludes reading
# (simple "write" implies reading)
rule {
  [dn children $Devices]
    [attrs userPassword]
      # This must be "write", not "=axz" as below with People's passwords.
      # This is because devices belong to "simpleSecurityObject"-objectClass,
      # which requires that each time these objects are updated,
      # "userPassword"-attribute must also be set (?!?), and it can not
      # be set unless admins also can read it.  (This is not perfect but
      # this access is for admins only and the password is hashed anyway).
      by $org_owners         write
      by $this_school_admins write

      by anonymous           auth
}

rule {
  [dn children $Servers]
    [attrs userPassword]
      by $org_owners write      # the same as above
      by anonymous   auth
}

rule {
  # Allow teachers to change student passwords, but only if they belong
  # to the same school and have the "set_student_password" permission set.
  [dn children $People]
    filter="(puavoEduPersonAffiliation=student)"
    [attrs userPassword]
      by [this_school_teachers_with_permission set_student_password] =axz
      by *                                                           none break
}

rule {
  # If an account is locked, deny authentication.
  [dn children $People]
    filter="(puavoLocked=TRUE)"
    [attrs userPassword]
      by anonymous none
      by *         none break
}

rule {
  # Allow some privileged folks to change other peoples' passwords.
  # Note that if admins were added to the same school where organisation
  # owners are, then they could change organisation owner passwords.
  [dn children $People]
    [attrs userPassword]
      by self                =axz
      by [puavo_dn pw-mgmt]  =axz
      by $org_owners         =axz
      by $this_school_admins =axz
      by anonymous           auth
}

rule {
  # Organisation owners can change system account passwords.
  [dn children $System_Accounts]
    [attrs userPassword]
      by $org_owners write      # see comment about "write" some rules above
      by anonymous   auth
}

rule {
  # This is needed for all who need to create users,
  # plus Samba in case of Windows compatibility.
  [dn exact sambaDomainName=${samba_domain}]
    [attrs sambaNextRid]
      by $org_owners write
      by $admins     write
      by *           none break
}

rule {
  [dn exact sambaDomainName=${samba_domain}]
      by $org_owners write
      by $admins     read
}

rule {
  # Needed by those who can create/remove admin users.
  [dn exact $Domain_Admins]
    [attrs memberUid]
      by $org_owners write
}

rule {
  # For those who can manage users.
  [dn exact $Domain_Admins]
      by $org_owners read
      by $admins     read
}

rule {
  # Needed by those who can create/remove users.
  [dn exact $Domain_Users]
    [attrs memberUid]
      by $org_owners write
      by $admins     write
}

rule {
  # For those who can manage users.
  [dn exact $Domain_Users]
      by $org_owners read
      by $admins     read
}

# Kerberos Realms related rules

rule {
  [dn subtree "ou=Kerberos Realms"]
    [attrs krbExtraData        \
           krbLastFailedAuth   \
           krbLoginFailedCount \
           krbLastSuccessfulAuth]
      by [puavo_dn kdc] write
      by *              none break
}

rule {
  [dn subtree "ou=Kerberos Realms"]
      by [puavo_dn kadmin] write
      by [puavo_dn kdc]    read
      by *                 none break
}

# Allow removing kerberos principals for those who can do user management.
# Use filter so that only normal user kerberos principals may be removed,
# not any service principals.

rule {
  [dn exact "cn=${kerberos_realm},ou=Kerberos Realms"]
    filter="(!(krbPrincipalName=*/*@${kerberos_realm}))"
    [attrs children]
      by $org_owners write
      by $admins     write
}

rule {
  [dn children "cn=${kerberos_realm},ou=Kerberos Realms"]
    filter="(!(krbPrincipalName=*/*@${kerberos_realm}))"
    [attrs entry]
      by $org_owners write
      by $admins     write
}

# Allow admins to read krbLastSuccessfulAuth and krbPrincipalName of
# all users (but not of service principals).
rule {
  [dn subtree "ou=Kerberos Realms"]
    filter="(!(krbPrincipalName=*/*@${kerberos_realm}))"
    [attrs entry krbLastSuccessfulAuth krbPrincipalName objectClass]
      by $org_owners read
      by $admins     read
}

# rules for ou=Hosts (and subtrees)

# give wide read access to these device-related puavo DNs
# (note that these can not look under ou=Samba,ou=Hosts)
rule {
  [dn subtree $Hosts]
      by [puavo_dn monitor]    read
      by [puavo_dn statistics] read
      by *                     none break
}

# rules for ou=Devices,ou=Hosts
# (should be before specific $Hosts ACLs, because $Devices is in
# ou=Hosts subtree)

rule {
  # For those who need to register new devices and modify device settings.
  [dn exact $Devices]
    [attrs children]
      by $org_owners write
      by $admins     write
      by anonymous   none
      by *           none break
}

rule {
  [dn exact $Devices]
    [attrs children entry objectClass ou]
      by $org_owners           read
      by [dn children $People] read
      by [dn children $Hosts]  read
      by [puavo_dn examomatic] read
      by [systemgroup devices] read
      by anonymous             auth
}

rule {
  # Allow devices to write to write their own "puavoDeviceHWInfo".
  [dn children $Devices]
    [attrs puavoDeviceAvailableImage \
           puavoDeviceCurrentImage   \
           puavoDeviceHWInfo         \
           puavoDeviceMonitorsXML    \
           puavoDevicePrimaryUser    \
           puavoDeviceReset]
      by self write
      by *    none break
}

rule {
  # We must allow all admins to read all hostnames in an organisation.
  # This is so that registration can check if a hostname is already
  # reserved, and give out a proper error message.
  [dn children $Devices]
    [attrs entry objectClass puavoHostname puavoId]
      by $org_owners         write
      by $this_school_admins write
      by $admins             read
      by *                   none break
}

rule {
  # Allow examomatic to read some device attributes.
  [dn children $Devices]
    [attrs entry objectClass puavoConf puavoDeviceType puavoDisplayName puavoHostname puavoId puavoSchool]
      by [puavo_dn examomatic] read
      by *                     none break
}

rule {
  # Allow changing device settings for organisation owners and admins
  # who are in the same school as the device.
  [dn children $Devices]
      by $org_owners           write
      by $this_school_admins   write
      by self                  read
      by [systemgroup devices] read
      by anonymous             auth
}

# rules for ou=Servers,ou=Hosts
# (should be before $Hosts, because $Servers is in ou=Hosts subtree)

# Only organisation owners can create new servers, but other should be
# able to read some information on them, for example to access printing
# permissions.

rule {
  [dn exact $Servers]
    [attrs children]
      by $org_owners write
      by $admins     read
      by anonymous   none
      by *           none break
}

rule {
  [dn exact $Servers]
    [attrs children entry objectClass ou]
      by $org_owners           read
      by $admins               read
      by [puavo_dn cert-mgmt]  read
      by [systemgroup servers] read
      by anonymous             auth
}

rule {
  [dn children $Servers]
    [attrs entry objectClass ou puavoDeviceType puavoEduPersonPrimarySchool \
           puavoExport puavoHostname puavoId puavoSchool]
      by $org_owners write
      by $admins     read

      # XXX This is needed by /v3/sessions printer queues server information.
      # XXX It is unclear if this is needed in the real world, but old ACLs
      # XXX have this and some session tests break without this.
      # XXX If this is actually needed maybe the attribute list could
      # XXX be made shorter.
      by [devicetype_set laptop] read

      by * none break
}

rule {
  # Allow cert-mgmt to read some server information.
  [dn children $Servers]
    [attrs entry objectClass puavoHostname puavoNextcloudSubDomain]
      by [puavo_dn cert-mgmt] read
      by *                    none break
}

rule {
  [dn children $Servers]
      by $org_owners           write
      by [systemgroup servers] read
      by anonymous             auth
}

# rules for ou=Hosts specifically

rule {
  [dn exact $Hosts]
      by $org_owners           read
      by $admins               read
      by [dn children $Hosts]  read
      by [systemgroup devices] read
      by [systemgroup servers] read
}

# rules for ou=Printers

rule {
  [dn exact $Printers]
    [attrs entry objectClass ou]
      by $org_owners                 read
      by [dn children $Hosts]        read
      by [dn children $People]       read
      by [systemgroup printerqueues] read
}

rule {
  [dn exact $Printers]
    [attrs children]
      by $org_owners                 write
      by [dn children $Hosts]        read
      by [dn children $People]       read
      by [systemgroup printerqueues] read
}

rule {
  [dn children $Printers]
      by $org_owners                 write
      by [dn children $Hosts]        read
      by [dn children $People]       read
      by [systemgroup printerqueues] read
}

# rules for ou=Groups

rule {
  # For those who can manage users.
  [dn exact $Groups]
    [attrs children]
      by $org_owners write
      by $admins     write
      by *           none break
}

rule {
  [dn exact $Groups]
    [attrs children entry objectClass ou]
      by $org_owners                read
      by [puavo_dn examomatic]      read
      by [puavo_dn puavo]           read
      by [puavo_dn statistics]      read
      by [dn children $People]      read
      by [dn children $Hosts]       read
      by [systemgroup getent]       read
      by [systemgroup nextcloud]    read
      by [systemgroup puavo2google] read
}

# deny $admins write access to some school attributes
rule {
  [dn subtree $Groups]
    filter="(objectClass=puavoSchool)"
    [attrs puavoBillingInfo puavoSchoolAdmin]
      by $org_owners write      # must be explicit here, org_owners are admins
      by $admins     read
      by *           none break
}

# Do not allow read access to People and Hosts to group membership
# lists.  We do not want ordinary people to be able to list all usernames
# in an organisation (but for teachers this is okay).
rule {
  [dn subtree $Groups]
    [attrs member memberUid]
      by $org_owners write
      by $admins     write      # XXX maybe this_school_admins instead?
      by $teachers   read

      # Users must be able to check if they belong to a particular group.
      # This allows users to read the DNs of other users that belong to the
      # same group, but not usernames or other such user information.
      by set="this/member & user" read

      by [dn children $People]  none
      by [dn children $Devices] none
      by *                      none break
}

rule {
  # allow examomatic user to read some school information
  [dn subtree $Groups]
    filter="(objectClass=puavoSchool)"
    [attrs cn displayName entry objectClass puavoId]
      by [puavo_dn examomatic] read
      by *                     none break
}

rule {
  # allow nextcloud user to read some group information
  [dn subtree $Groups]
    [attrs cn displayName entry member memberUid objectClass]
      by [systemgroup nextcloud] read
      by *                       none break
}

rule {
  # allow puavo2google to read some group information
  [dn subtree $Groups]
    [attrs cn displayName entry member memberUid objectClass \
           puavoEduGroupType puavoExternalId puavoId puavoLocale puavoSchool \
           puavoSchoolCode]
      by [puavo_dn puavo2google] read
      by *                       none break
}

rule {
  # allow statistics to read some group information
  [dn subtree $Groups]
    [attrs cn displayName entry objectClass puavoActiveService puavoConf \
           puavoDeviceImage puavoId puavoImageSeriesSourceURL puavoSchool \
           puavoTag]
      by [puavo_dn statistics] read
      by *                     none break
}

rule {
  # Write permissions for those who can manage users.
  [dn subtree $Groups]
      by $org_owners             write
      by $admins                 write  # XXX maybe this_school_admins instead?
      by [puavo_dn puavo]        read
      by [puavo_dn pw-mgmt]      read
      by [dn children $People]   read
      by [dn children $Hosts]    read
      by [systemgroup getent]    read
}

# rules for ou=People

rule {
  # Write permissions for those who can manage users.
  [dn exact $People]
    [attrs children]
      by $org_owners write
      by $admins     write
      by *           none break
}

rule {
  [dn exact $People]
    [attrs children entry objectClass ou]
      by [puavo_dn email-mgmt]      read
      by [puavo_dn mfa-mgmt]        read
      by [puavo_dn puavo]           read
      by [puavo_dn pw-mgmt]         read
      by [puavo_dn statistics]      read
      by [systemgroup addressbook]  read
      by [systemgroup auth]         read
      by [systemgroup getent]       read
      by [systemgroup nextcloud]    read
      by [systemgroup puavo2google] read
      by [dn children $People]      read
      by [dn children $Hosts]       read
      by anonymous                  auth
}

rule {
  [dn subtree $People]
    [attrs entry objectClass uid uidNumber]
      # Laptops should be able to read usernames.  This is because laptops
      # may decide who its primary user is, and they can not set this
      # information unless they can read user dn, which is not possible
      # unless both "username" and "dn" are readable by laptop.
      # Laptops should be able to read user numeric ids.  This is because
      # data owned by users that have been removed should be deleted (in time)
      # from laptops, and laptops can determine this by getting a list of
      # all current user uids in an organisation.
      by [devicetype_set laptop] read

      by anonymous auth
      by *         none break
}

rule {
  # allow statistics to read user role
  [dn subtree $People]
    [attrs entry objectClass puavoEduPersonAffiliation \
                 puavoEduPersonPrimarySchool puavoId puavoSchool uid]
      by [puavo_dn statistics] read
      by *                     none break
}

# some permissions to "addressbook"
rule {
  [dn subtree $People]
    [attrs cn                               \
           displayName                      \
           entry                            \
           givenName                        \
           jpegPhoto                        \
           mail                             \
           objectClass                      \
           preferredLanguage                \
           puavoEduPersonPersonnelNumber    \
           puavoEduPersonReverseDisplayName \
           puavoExternalData                \
           puavoExternalId                  \
           puavoLearnerId                   \
           puavoLocale                      \
           puavoUuid                        \
           sn                               \
           telephoneNumber                  \
           uid]
      by [systemgroup addressbook] read
      by *                         none break
}

# some permissions to "auth"
rule {
  [dn subtree $People]
    [attrs eduPersonPrincipalName    \
           entry                     \
           objectClass               \
           puavoEduPersonAffiliation \
           puavoRemovalRequestTime   \
           uid]
      by [systemgroup auth] read
      by *                  none break
}

# some permissions to service "getent"
# XXX do we need still this?  If still in use, should the name be changed?
rule {
  [dn subtree $People]
    [attrs displayName                      \
           eduPersonPrincipalName           \
           entry                            \
           gidNumber                        \
           givenName                        \
           homeDirectory                    \
           loginShell                       \
           objectClass                      \
           puavoEduPersonAffiliation        \
           puavoEduPersonPrimarySchool      \
           puavoEduPersonReverseDisplayName \
           puavoId                          \
           puavoLocked                      \
           puavoRemovalRequestTime          \
           puavoSchool                      \
           sn                               \
           uid                              \
           uidNumber]
      by [systemgroup getent] read
      by *                    none break
}

# some permissions to "nextcloud"
rule {
  [dn subtree $People]
    [attrs cn                \
           displayName       \
           entry             \
           entryUuid         \
           givenName         \
           jpegPhoto         \
           mail              \
           objectClass       \
           preferredLanguage \
           puavoLocked       \
           puavoSchool       \
           sn                \
           telephoneNumber   \
           uid]
      by [systemgroup nextcloud] read
      by *                       none break
}

# some permissions to "puavo2google"
rule {
  [dn subtree $People]
    [attrs cn                                  \
           entry                               \
           givenName                           \
           mail                                \
           puavoDoNotDelete                    \
           puavoEduPersonAccountExpirationTime \
           puavoEduPersonAffiliation           \
           puavoEduPersonPrimarySchool         \
           puavoExternalId                     \
           puavoId                             \
           puavoLocale                         \
           puavoLocked                         \
           puavoRemovalRequestTime             \
           puavoSchool                         \
           sn                                  \
           uid]
      by [systemgroup puavo2google] read
      by *                          none break
}

# rules needed by email-mgmt (email management user)
# and mfa-mgmt (multi-factor authentication management user)
rule {
  [dn subtree $People]
    [attrs entry objectClass puavoId uid]
      by [puavo_dn email-mgmt] read
      by [puavo_dn mfa-mgmt]   read
      by *                     none break
}

rule {
  [dn subtree $People]
    [attrs mail puavoPrimaryEmail puavoVerifiedEmail]
      by [puavo_dn email-mgmt] write
      by *                     none break
}

rule {
  [dn subtree $People]
    [attrs puavoMFAEnabled]
      by [puavo_dn mfa-mgmt] write
      by *                   none break
}

# rules needed by puavo (user account used by puavo-web and puavo-rest)
# XXX Not sure why these are needed or if puavo and pw-mgmt need a
# XXX matching set of attributes, but ACL tests explicitly tests for
# XXX most of these.
rule {
  [dn subtree $People]
    [attrs eduPersonPrincipalName      \
           entry                       \
           gidNumber                   \
           givenName                   \
           homeDirectory               \
           jpegPhoto                   \
           loginShell                  \
           mail                        \
           modifyTimestamp             \
           objectClass                 \
           preferredLanguage           \
           puavoAcceptedTerms          \
           puavoAdminOfSchool          \
           puavoEduPersonAffiliation   \
           puavoEduPersonPrimarySchool \
           puavoExternalData           \
           puavoExternalId             \
           puavoId                     \
           puavoLearnerId              \
           puavoLocale                 \
           puavoLocked                 \
           puavoMFAEnabled             \
           puavoPrimaryEmail           \
           puavoRemovalRequestTime     \
           puavoSchool                 \
           puavoTimezone               \
           puavoUuid                   \
           puavoVerifiedEmail          \
           sn                          \
           telephoneNumber             \
           uid                         \
           uidNumber]
      by [puavo_dn puavo] read
      by *                none break
}

# rules needed by pw-mgmt (password management user)
# many of these can probably be removed
rule {
  [dn subtree $People]
    [attrs eduPersonPrincipalName      \
           entry                       \
           gidNumber                   \
           givenName                   \
           homeDirectory               \
           jpegPhoto                   \
           loginShell                  \
           mail                        \
           objectClass                 \
           preferredLanguage           \
           puavoAcceptedTerms          \
           puavoEduPersonAffiliation   \
           puavoEduPersonPrimarySchool \
           puavoId                     \
           puavoLocale                 \
           puavoSchool                 \
           sn                          \
           telephoneNumber             \
           uid                         \
           uidNumber]
      by [puavo_dn pw-mgmt] read
      by *                  none break
}

# allow users to change some of their own information
rule {
  [dn subtree $People]
    [attrs jpegPhoto          \
           mail               \
           preferredLanguage  \
           puavoAcceptedTerms \
           puavoCitrixId      \
           puavoLocale        \
           telephoneNumber]
      by self write
      by *    none break
}

rule {
  # Only organisation owners can modify admin user special permissions
  # (and others can not even read them, except if self is an admin).
  [dn subtree $People]
    [attrs puavoAdminPermissions]
      by $org_owners write
      by $selfadmins read
}

rule {
  # Only organisation owners and admins of the teacher's school can
  # modify teacher user special permissions (and others can not even
  # read them, except if self is a teacher).
  [dn subtree $People]
    [attrs puavoTeacherPermissions]
      by $org_owners         write
      by $this_school_admins write
      by $selfteachers       read
      by [puavo_dn puavo]    read
}

# Give hosts access to owner information (so that org.json can contain
# admin users).
# XXX Note that while the need is for owners, this checks the admin status
# XXX instead.  In general it is wrong to have owners who are not admins,
# and Puavo enforces this to some extent, but not on database level.
rule {
  [dn subtree $People]
    filter="(puavoEduPersonAffiliation=admin)"
      by [dn children $Hosts] read
      by *                    none break

}

rule {
  # Allow organisation owners and admins in the same school to
  # change peoples' information.  Allow read access to school teachers
  # and admins using the import tool.
  [dn subtree $People]
      by $org_owners                           write
      by $this_school_admins                   write
      by $this_school_teachers                 read   # for /v3/my_school_users
      by [admins_with_permission import_users] read
      by self                                  read
}

# rules for ou=Desktops

# We used to have some other branches here, but only ou=Files,ou=Desktops
# ended up in permanent use, and this contains the external files.
# Organisation owners can write these, hosts can read.

rule {
  [dn exact $Files]
    [attrs children]
      by $org_owners write
      by *           none break
}

rule {
  [dn exact $Files]
    [attrs children entry objectClass ou]
      by $org_owners          read
      by [dn children $Hosts] read
}

rule {
  [dn children $Files]
      by $org_owners          write
      by [dn children $Hosts] read
}

rule {
  [dn exact $Desktops]
    [attrs children entry objectClass ou]
      by $org_owners          read
      by [dn children $Hosts] read
}

# rules for ou=System Accounts and ou=System Groups

rule {
  [dn subtree $System_Accounts]
      by $org_owners      write
      by [puavo_dn puavo] read
}

rule {
  [dn subtree $System_Groups]
    [attrs member]
      by $org_owners         write
      by $this_school_admins write
}

rule {
  [dn subtree $System_Groups]
      by $org_owners         read
      by $this_school_admins read
}

# rules for the organisation entry
# "+dsx" = "disclose search auth"

rule {
  # Do not allow organisation owners to change some organisational
  # attributes which are meant to be set once only.
  [dn]
    [attrs entry                        \
           objectClass                  \
           puavoDomain                  \
           puavoKadminPort              \
           puavoKerberosRealm           \
           puavoRemoteDesktopPrivateKey \
           puavoRemoteDesktopPublicKey  \
           sambaDomainName]
      by $org_owners read
      by *           none break
}

rule {
  # Allow cert-mgmt to read only some organisation specific info.
  [dn]
    [attrs entry objectClass puavoDomain puavoNextcloudDomain puavoOfficeDomain]
      by [puavo_dn cert-mgmt] read
      by *                    none break
}

rule {
  # Allow examomatic to read only some organisation specific info.
  [dn]
    [attrs entry objectClass owner puavoOrganisationOID]
      by [puavo_dn examomatic] read
      by *                     none break
}

rule {
  # Allow organisation owners to change many organisation settings,
  # and most other entities to read them.
  [dn]
      by $org_owners           write
      by [dn children $People] read
      by [dn children $Hosts]  read
      by [puavo_dn kadmin]     read
      by [puavo_dn kdc]        read
      by [puavo_dn monitor]    read
      by [puavo_dn puavo]      read
      by [puavo_dn statistics] read
      by [systemgroup getent]  read
      by [systemgroup orginfo] read
      by *                     +dsx
}

#
# print rules
#

set rulenum 0
foreach rule $rules {
  set trimmed_rule [string trim "$rule"]
  puts "{${rulenum}}to ${trimmed_rule}"
  incr rulenum
}
