Der nachfolgende Code ist frei verfügbar, solange er nicht kommerziell verwendet wird! Falls er so wie veröffentlicht, in Teilen oder abgewandelt zum Einsatz kommt, würde ich mich sehr über eine kurze E-Mail freuen.
Datenmodel:
Beschreibung:
In der Regel reicht die User-Verwaltung einer Datenbank nicht aus, um die Zugriffsberechtigungen innerhalb einer auf die Datenbank zugreifenden Application steuern zu können. Daher muß häufig eine eigene Userverwaltung eingerichtet werden.
Die hier vorgestellte User-Verwaltung ermöglicht es User in User-Gruppen zusammenzufassen und diesen User-Gruppen Rechte (Permissions) zuzuweisen. Die zur notwendigen Verwaltung und Nutzung benötigten Proceduren/Funktionen befinden sich in dem Package users.
Da in einigen Branchen (z.B. Banken/Versicherungen) sehr viel Wert auf die Nachvollziehbarkeit von Änderungen gelegt wird, werden alle Berechtigungsänderungen über Trigger in History-Tabellen protokolliert.
Hinweis: Dieses Berechtigungskonzept ist so in einem von mir durchgeführten Projekt vom Bundesamt für das Finanzwesen (Bafin) abgenommen worden.
Application-Login:
In einer klassischen Client-Server Architektur sollten dem User aus Sicherheitsgründen möglichst wenig Rechte direkt auf der Datenbank eingeräumt werden. Stattdessen sollten die Tätigkeiten des Users, die sich aus den Arbeitsprozessen ergeben, in einer guten Ablaufführung der Client-Applikation kontrolliert durchgeführt werden. Dazu benötigt die Applikation mehr Rechte als man in der Regel dem einzelnen Usern zugestehen möchte.
Daher bietet es sich an, die Anmeldung der Client-Applikation an die Datenbank mit einem den für den User nicht sichtbaren Applikations-Login durchzuführen. Die Rechte, die der sich jeweils anmeldende User dann erhält, werden dann über ein wie hier vorgestelltes Berechtigungskonzept verwaltet.
In einem sicherheitsrelevantem Umfeld (Banken/Versicherungen etc.) wird darüberhinaus auch eine strikte Trennung zwischen Entwicklungs- und Produktonssystem gefordert. Login und Passwort eines solchen Applikations-Login dürfen dem Entwickler nicht bekannt sein, sondern müssen vom Administrator der Produktionsumgebung frei wähl- und änderbar sein, ohne das der Entwickler davon Kenntnis erhalten darf. Auch darf der Applikations-Login nicht unverschlüsselt auf dem Client hinterlegt werden. Dies wäre z.B. im Falle einer Passwort-Änderung ein erheblicher adminstrativer Aufwand, da es ja auf jedem Client geändert werden muß. Darüberhinaus könnte sich der User so auch ganz leicht die Zugangsparameter und somit die erweiterten Berechtigungen des Applikations-Login besorgen. Es ist also darauf zu achten das:
dem Entwickler kein Account bekannt ist, der ihm relevanten Zugriff auf die Produktionsumgebung gewährt
der User keine Möglicheiten erhält sich die Accountdaten für den Applikations-Account mit seinen erweiterten Berechtigungen zu beschaffen
der Administrator an zentraler Stelle freie Verfügungsgewalt über die Accountdaten des Applikations-Account erhält
Bei dem hier vorgestellten Konzept erhält der Entwickler lediglich Kenntnis über die Account-Daten eines anonymisierten Anmelde-Account, der nur die Ausführrechte für die Datenbankfunktion:
FUNCTION get_application_login
(
login IN OUT tuse_user.use_login%TYPE,
password IN OUT tuse_user.use_password%TYPE
) RETURN INTEGER;
hat. Die Accountdaten für diesen stark eingeschränkten Anmelde-Account kann der Entwickler z.B. fest im Code (oder entsprechend verschlüsselt im Konfigurationsfile der Client-Applikation) hinterlegen. Da dieser Anmelde-Account faktisch 'rechtelos' ist, kann auf eine Änderung des Passwortes in vorgeschriebenen Zeitintervallen verzichtet werde.
Die Client-Applikation führt nun die folgenden Schritte aus:
Im ersten Schritt meldet sich die Client-Applikation nun mit diesem Anmelde-Account an die Datenbank an.
Die Client-Applikation fragt nun die personalisierten Accountdaten des Users ab.
Mit den personalisierten Accountdaten des Users ruft die Client-Applikation die Funktion users.get_application_login auf.
Die Funktion verifiziert nun die personalisierten Accountdaten gegen die in tuse_user hinterlegten Daten.
Wird die Zugangsberechtigung des Users von der Funktion bestätigt, dann übergibt sie die Account-Daten für den Applikations-Login an die Client-Applikation.
Die Client-Applikation melde sich mit dem Anmelde-Account von der Datenbank ab.
Anschließend meldet sie sich mit den Daten des Applikations-Account erneut an und hat damit nun dessen erweiterten Berechtigungen, ohne das User und Entwickler von den Accountdaten Kenntnis erhalten müssen.
Innerhalb der Client-Applikation können nun die userabhängigen Rechte (Permissions) über das hier vorgestellte Berechtigungskonzept verwaltet und gesteuert werden. Dazu verwendet die Client-Applikation die Datenbankfunktion users.check_permission.
Unabhängig vom Administrator der Produktionsdatenbank können jetzt nur Entwickler und User zusammen an die Daten des Anmelde-Accounts gelangen. Da es sich hier aber um klar getrennte Aufgabengebiete handelt ist hier die nötige Funktionstrennung eingehalten.
Vorraussetzungen:
Zur Ablage einiger Konfigurationsparameter wird das Package Parameter benötigt. Die folgenden Werte werden dort hinterlegt:
Parametergruppe
Parameter
Wert
Beschreibung
User-Login
Min.Password-Length
8
Minimale Passwortlänge
Password-History
10
Anzahl der historischen Passwörter die nicht mit einem neuen Passwort übereinstimmen dürfen
Max.-Failed-Login
3
Maximale Anzahl der erlaubten fehlerhaften Logins
PW-Expired-Time
90
Zeit in Tagen, nach denen das Passwort abläuft
App.-Login
UNKNOWN
Login mit umfangreichen Berechtigungen für die Applikation (siehe auch: Application-Login)
Enthält die notwendigen Benutzer-Informationen wie Name, Login, Password, etc.
Spalte
Datentyp
Comment
use_id
NUMBER(6)
Identifier
use_name
VARCHAR2(32)
Name des Users
use_login
VARCHAR2(12)
Login des Users
use_password
VARCHAR2(16)
Password des Users in MD5 kodiert
use_password_changed
TIMESTAMP
letze Änderungsdatum des User-Password
use_password_never_expired
VARCHAR2(4)
no
:
Password läuft nach einer voreingestellten Zeit ab
yes
:
Password läuft nie ab
use_status
VARCHAR2(16)
Login-Status des User:
locked
:
User locked
pw_must_changed
:
User muß Password bei der nächsten Anmeldung ändern
active
:
User active
use_email
varchar(64)
E-Mail Adresse des Users
use_email_active
VARCHAR2(8)
active
:
E-Mail active
inactive
:
E-Mail inactive
tusg_user_group
Enthält die Definition der Usergruppen.
Spalte
Datentyp
Comment
usg_id
NUMBER(6)
Identifier
usg_label
VARCHAR2(16)
Name der Usergruppe
usg_description
VARCHAR2(64)
Beschreibung der Usergruppe
tupe_user_permission
Enthält die Definition der Berechtigungen.
Spalte
Datentyp
Comment
upe_id
NUMBER(6)
Identifier
upe_label
VARCHAR2(16)
Name der Berechtigung
usg_description
VARCHAR2(64)
Beschreibung der Berechtigungen
tuga_use_usg_assign
Ordnet User Usergruppen zu.
Spalte
Datentyp
Comment
uga_use_id
NUMBER(6)
Referenz-ID zu [tuse_user] (User)
uga_usg_id
NUMBER(6)
Referenz-ID zu [tusg_user_group] (Usergruppen)
tupa_usg_upe_assign
Ordnet Usergruppen Berechtigungen zu.
Spalte
Datentyp
Comment
upa_usg_id
NUMBER(6)
Referenz-ID zu [tusg_user_group] (Usergruppen)
upa_upe_id
NUMBER(6)
Referenz-ID zu [tupe_user_permissions] (Berechtigungen)
tulg_user_login_log
Enthält für jeden User ein Protokoll über jeden erfolgreichen oder fehlerhaftem Login.
Spalte
Datentyp
Comment
ulg_use_id
NUMBER(6)
Referenz-ID zu [tuse_user] (User)
ulg_datetime_login
TIMESTAMP
Zeitpunkt des versuchten Logins
ulg_status
VARCHAR2(8)
Ergebnis des Loginprozesses:
ok
:
Login war erfolgreich
failed
:
Login war nicht erfolgreich
tuph_user_pw_history
Enthält die historisierten Passwords zur Kontrolle auf wiederverwendete Passwords.
Spalte
Datentyp
Comment
uph_use_id
NUMBER(6)
Referenz-ID zu [tuse_user] (User)
uph_password_changed
TIMESTAMP
Änderungsdatum des User-Password
uph_password
VARCHAR2(48)
historisiertes Password des Users in MD5 kodiert
Historietabellen (h...)
Änderungen auf den Berechtigungstabellen werden über Trigger in Historietabellen protokolliert:
Tabelle
Trigger
Historietabelle
tuse_user
trg_use_user
huse_user
tusg_user_group
trg_usg_user_group
husg_user_group
tuga_use_usg_assign
trg_uga_use_usg_assign
huga_use_usg_assign
tupa_usg_upe_assign
trg_upa_usg_upe_assign
hupa_usg_upe_assign
Zusätzlich zu den Spalten der Arbeitstabellen enthalten die Historietabellen noch die Information über den User der die Änderung vorgenommen hat, dem Änderungszeitpunkt und dem ausführenden SQL-Statement. Wegen der besseren Übersicht werden dabei nur die Spalten befüllt, deren Inhalt geändert wurde. Alle anderen werden mit NULL befüllt.
Spalte
Datentyp
Comment
.
.
.
.
.
.
.
.
.
..._user
VARCHAR2(32)
User. der die Änderung durchgeführt hat
..._timestamp
TIMESTAMP
Zeitpunkt der Änderung
..._action
CHAR(1)
SQL der Änderung (I = Insert, U = Update, D = Delete)
Proceduren/ Funktionen:
Die Proceduren / Funktionen befinden sich im Package users:
add_user
FUNCTION add_user
(
name IN tuse_user.use_name%TYPE,
login IN tuse_user.use_login%TYPE,
password IN OUT tuse_user.use_password%TYPE,
password_never_expired IN tuse_user.use_password_never_expired%TYPE DEFAULT'no',
status IN tuse_user.use_status%TYPE DEFAULT'active',
email IN tuse_user.use_email%TYPE DEFAULT NULL,
email_active IN tuse_user.use_email_active%TYPE DEFAULT'inactive',
) RETURN tuse_user.use_id%TYPE;
Fügt einen neuen User in das Berechtigungssystem ein. Wird kein Passwort übergeben, dann wird mittels der packageeigenen, nicht öffentlichen Funktion create_default_password ein Passwort erzeugt und zurückgegeben. (Hinweis: Die Werte email und email_active füllen hier nur die Spalten in der Tabelle tuse_user. Das Package users benötigt diese Werte ansonsten nicht.)
Returnwerte:
>0
=
add login successfull, returns the User-ID (use_id)
0
=
login exists
-1
=
password not confirm to the password-rules
change_password
FUNCTION change_password
(
login IN tuse_user.use_login%TYPE,
password_old IN tuse_user.use_password%TYPE,
password_new IN tuse_user.use_password%TYPE
) RETURN INTEGER;
Ändert das Passwort für einen bestehenden Login (login). Dabei wird die Änderung nur zugelassen, wenn das neue Passwort den definierten Passwort-Regeln entspricht. Die Passwort-Regeln können in der nicht öffentlichen Funktion check_password_roles den jeweiligen Erfodernissen angepaßt werde. Desweiteren wird überprüft, ob das neue Passwort ungleich dem alten Passwort ist und auch nicht gleich einer festgelegten Anzahl der zuletzt benutzten Passwörtern entspricht. Die Anzahl der überprüften historischen Passwörtern wird in der Parameter-Tabelle hinterlegt.
Returnwerte:
1
=
successful
0
=
login not defined
-1
=
password(new) not confirm to the password-rules
-2
=
password(old) incorrect
-3
=
password(new) = Password(old)
-4
=
password(new) = one of the last X Passwords in the Password-History
check_login
FUNCTION check_login
(
login IN tuse_user.use_login%TYPE,
password IN tuse_user.use_password%TYPE
) RETURN INTEGER;
Überprüft, ob ein User mit dem login und password existiert. Dabei wird auch abgeprüft, ob das Passwort abgelaufen ist und geändert werden muß. Wird die Funktion zu einem Login mehrmals hintereinander mit einem falschen Passwort aufgerufen (sie Anzahl der erlaubten Fehlanmeldungen wird in der Parameter-Tabelle hinterlegt), dann wird der Account gesperrt.
Returnwerte:
1
=
successful
0
=
login not defined
-1
=
password incorrect
-2
=
login successful but password expired (older then X days), please change it
-3
=
login successful but password must change, please change it
-4
=
login locked
-5
=
login locked after X failed logins
get_application_login
FUNCTION get_application_login
(
login IN OUT tuse_user.use_login%TYPE,
password IN OUT tuse_user.use_password%TYPE
) RETURN INTEGER;
Überprüft, ob ein User mit dem login und password existiert. War der Login erfolgreich werden login und password mit den aus der Parameter-Tabelle ausgelesenen Werten zurückgegeben. Siehe dazu auch: Application-Login
Returnwerte:
1
=
successful
0
=
login not defined
-1
=
password incorrect
-2
=
login successful but password expired (older then X days), please change it
-3
=
login successful but password must change, please change it
-4
=
login locked
-5
=
login locked after X failed logins
set_user_group
FUNCTION set_user_group
(
group_id IN tusg_user_group.usg_id%TYPE DEFAULT NULL,
group_label IN tusg_user_group.usg_label%TYPE DEFAULT NULL,
group_description IN tusg_user_group.usg_description%TYPE DEFAULT NULL
) RETURN INTEGER;
Wenn die Gruppen-ID group_id = NULL ist, dann wird eine neue User-Gruppe hinzugefügt. Ist die Gruppen-ID group_id != NULL, dann wird der bestehende Eintrag entsprechend der übergebenen Werte modifiziert.
Returnwerte:
>0
=
add group successfull, returns the Group-ID (usg_id)
-1
=
new group can not be created if label is NULL
-2
=
new group can not be created because there is a group with this label
-3
=
group can not be updated because there is a group with this label
-4
=
cannot update group, because the ID does not exists
set_user_permission
FUNCTION set_user_permission
(
permission_id IN tupe_user_permission.usg_id%TYPE DEFAULT NULL,
permission_label IN tupe_user_permission.usg_label%TYPE DEFAULT NULL,
permission_description IN tupe_user_permission.usg_description%TYPE DEFAULT NULL
) RETURN INTEGER;
Wenn die Berechtigungs-ID permission_id = NULL ist, dann wird eine neue User-Berechtigung hinzugefügt. Ist die Berechtigungs-ID permission_id != NULL, dann wird der bestehende Eintrag entsprechend der übergebenen Werte modifiziert.
Returnwerte:
>0
=
add permission successfull, returns the Permission-ID (upe_id)
-1
=
new permission can not be created if label is NULL
-2
=
new permission can not be created because there is a permission with this label
-3
=
permission can not be updated because there is a permission with this label
-4
=
cannot update permission, because the ID does not exists
assign_user_to_group
FUNCTION assign_user_to_group
(
login IN tuse_user.use_login%TYPE,
group_label IN tusg_user_group.usg_label%TYPE,
assign IN INTEGER DEFAULT1
) RETURN INTEGER;
assign
=
1
:
Die User-Gruppe group_label wird dem User login zugeordnet
assign
=
0
:
Wenn Die User-Gruppe group_label dem User login zugeordnet ist, dann wird sie aufgehoben. Ist Die User-Gruppe group_label dem User login nicht zugeordnet ist, dann wird sie zugeordnet.
assign
=
-1
:
Die Zuordnung User-Gruppe group_label / User login wird aufgehoben
Returnwerte:
1
=
successful
0
=
no changes, because mapping is like status in assign
-1
=
login does not exist
-2
=
group does not exist
assign_permission_to_group
FUNCTION assign_permission_to_group
(
permission_label IN tupe_permission.upe_label%TYPE,
group_label IN tusg_user_group.usg_label%TYPE,
assign IN INTEGER DEFAULT1
) RETURN INTEGER;
assign
=
1
:
Die User-Gruppe group_label wird der Permission permission_label zugeordnet
assign
=
0
:
Wenn Die User-Gruppe group_label der Permission permission_label zugeordnet ist, dann wird sie aufgehoben. Ist Die User-Gruppe group_label der Permission permission_label nicht zugeordnet ist, dann wird sie zugeordnet.
assign
=
-1
:
Die Zuordnung User-Gruppe group_label / Permission permission_label wird aufgehoben
Returnwerte:
1
=
successful
0
=
no changes, because mapping is like status in assign
-1
=
permission does not exist
-2
=
group does not exist
check_permission
FUNCTION check_permission
(
login IN tuse_user.use_login%TYPE,
permission_label IN tupe_user_permission.usg_label%TYPE
) RETURN VARCHAR2;
Gibt zurück, ob ein User eine bestimmte Berechtigung hat.
Returnwerte:
1
=
user has permission
0
=
user has no permission
-1
=
login not defined
-2
=
permission not defined
-3
=
login successful but password must change, please change it
get_status_message
FUNCTION get_status_message
(
source IN VARCHAR2,
status_id IN INTEGER
) RETURN VARCHAR2;
Liefert für den Rückgabewert einer Funktion dieses Packages eine Klartextmeldung in englischer Sprache zurück. Hierbei entspricht source dem Funktionsnamen und status_id dem Rückgabewert dieser Funktion.