Add access levels (#19)

- Remove authorization level
This commit is contained in:
Daniel J. Summers 2022-07-16 15:51:58 -04:00
parent 07aff16c3a
commit 425223a3a8
7 changed files with 102 additions and 73 deletions

View File

@ -291,16 +291,16 @@ module Map =
/// Create a web log user from the current row in the given data reader /// Create a web log user from the current row in the given data reader
let toWebLogUser (rdr : SqliteDataReader) : WebLogUser = let toWebLogUser (rdr : SqliteDataReader) : WebLogUser =
{ id = WebLogUserId (getString "id" rdr) { id = WebLogUserId (getString "id" rdr)
webLogId = WebLogId (getString "web_log_id" rdr) webLogId = WebLogId (getString "web_log_id" rdr)
userName = getString "user_name" rdr userName = getString "user_name" rdr
firstName = getString "first_name" rdr firstName = getString "first_name" rdr
lastName = getString "last_name" rdr lastName = getString "last_name" rdr
preferredName = getString "preferred_name" rdr preferredName = getString "preferred_name" rdr
passwordHash = getString "password_hash" rdr passwordHash = getString "password_hash" rdr
salt = getGuid "salt" rdr salt = getGuid "salt" rdr
url = tryString "url" rdr url = tryString "url" rdr
authorizationLevel = AuthorizationLevel.parse (getString "authorization_level" rdr) accessLevel = AccessLevel.parse (getString "access_level" rdr)
} }
/// Add a possibly-missing parameter, substituting null for None /// Add a possibly-missing parameter, substituting null for None

View File

@ -20,7 +20,7 @@ type SQLiteWebLogUserData (conn : SqliteConnection) =
cmd.Parameters.AddWithValue ("@passwordHash", user.passwordHash) cmd.Parameters.AddWithValue ("@passwordHash", user.passwordHash)
cmd.Parameters.AddWithValue ("@salt", user.salt) cmd.Parameters.AddWithValue ("@salt", user.salt)
cmd.Parameters.AddWithValue ("@url", maybe user.url) cmd.Parameters.AddWithValue ("@url", maybe user.url)
cmd.Parameters.AddWithValue ("@authorizationLevel", AuthorizationLevel.toString user.authorizationLevel) cmd.Parameters.AddWithValue ("@accessLevel", AccessLevel.toString user.accessLevel)
] |> ignore ] |> ignore
// IMPLEMENTATION FUNCTIONS // IMPLEMENTATION FUNCTIONS
@ -31,10 +31,10 @@ type SQLiteWebLogUserData (conn : SqliteConnection) =
cmd.CommandText <- """ cmd.CommandText <- """
INSERT INTO web_log_user ( INSERT INTO web_log_user (
id, web_log_id, user_name, first_name, last_name, preferred_name, password_hash, salt, url, id, web_log_id, user_name, first_name, last_name, preferred_name, password_hash, salt, url,
authorization_level access_level
) VALUES ( ) VALUES (
@id, @webLogId, @userName, @firstName, @lastName, @preferredName, @passwordHash, @salt, @url, @id, @webLogId, @userName, @firstName, @lastName, @preferredName, @passwordHash, @salt, @url,
@authorizationLevel @accessLevel
)""" )"""
addWebLogUserParameters cmd user addWebLogUserParameters cmd user
do! write cmd do! write cmd
@ -96,14 +96,14 @@ type SQLiteWebLogUserData (conn : SqliteConnection) =
use cmd = conn.CreateCommand () use cmd = conn.CreateCommand ()
cmd.CommandText <- """ cmd.CommandText <- """
UPDATE web_log_user UPDATE web_log_user
SET user_name = @userName, SET user_name = @userName,
first_name = @firstName, first_name = @firstName,
last_name = @lastName, last_name = @lastName,
preferred_name = @preferredName, preferred_name = @preferredName,
password_hash = @passwordHash, password_hash = @passwordHash,
salt = @salt, salt = @salt,
url = @url, url = @url,
authorization_level = @authorizationLevel access_level = @accessLevel
WHERE id = @id WHERE id = @id
AND web_log_id = @webLogId""" AND web_log_id = @webLogId"""
addWebLogUserParameters cmd user addWebLogUserParameters cmd user

View File

@ -165,16 +165,16 @@ type SQLiteData (conn : SqliteConnection, log : ILogger<SQLiteData>) =
log.LogInformation "Creating web_log_user table..." log.LogInformation "Creating web_log_user table..."
cmd.CommandText <- """ cmd.CommandText <- """
CREATE TABLE web_log_user ( CREATE TABLE web_log_user (
id TEXT PRIMARY KEY, id TEXT PRIMARY KEY,
web_log_id TEXT NOT NULL REFERENCES web_log (id), web_log_id TEXT NOT NULL REFERENCES web_log (id),
user_name TEXT NOT NULL, user_name TEXT NOT NULL,
first_name TEXT NOT NULL, first_name TEXT NOT NULL,
last_name TEXT NOT NULL, last_name TEXT NOT NULL,
preferred_name TEXT NOT NULL, preferred_name TEXT NOT NULL,
password_hash TEXT NOT NULL, password_hash TEXT NOT NULL,
salt TEXT NOT NULL, salt TEXT NOT NULL,
url TEXT, url TEXT,
authorization_level TEXT NOT NULL); access_level TEXT NOT NULL);
CREATE INDEX web_log_user_web_log_idx ON web_log_user (web_log_id); CREATE INDEX web_log_user_web_log_idx ON web_log_user (web_log_id);
CREATE INDEX web_log_user_user_name_idx ON web_log_user (web_log_id, user_name)""" CREATE INDEX web_log_user_user_name_idx ON web_log_user (web_log_id, user_name)"""
do! write cmd do! write cmd

View File

@ -436,8 +436,8 @@ type WebLogUser =
/// The URL of the user's personal site /// The URL of the user's personal site
url : string option url : string option
/// The user's authorization level /// The user's access level
authorizationLevel : AuthorizationLevel accessLevel : AccessLevel
} }
/// Functions to support web log users /// Functions to support web log users
@ -445,16 +445,16 @@ module WebLogUser =
/// An empty web log user /// An empty web log user
let empty = let empty =
{ id = WebLogUserId.empty { id = WebLogUserId.empty
webLogId = WebLogId.empty webLogId = WebLogId.empty
userName = "" userName = ""
firstName = "" firstName = ""
lastName = "" lastName = ""
preferredName = "" preferredName = ""
passwordHash = "" passwordHash = ""
salt = Guid.Empty salt = Guid.Empty
url = None url = None
authorizationLevel = User accessLevel = Author
} }
/// Get the user's displayed name /// Get the user's displayed name
@ -463,3 +463,7 @@ module WebLogUser =
seq { match user.preferredName with "" -> user.firstName | n -> n; " "; user.lastName } seq { match user.preferredName with "" -> user.firstName | n -> n; " "; user.lastName }
|> Seq.reduce (+) |> Seq.reduce (+)
name.Trim () name.Trim ()
/// Does a user have the required access level?
let hasAccess level user =
AccessLevel.hasAccess level user.accessLevel

View File

@ -12,6 +12,51 @@ module private Helpers =
Convert.ToBase64String(Guid.NewGuid().ToByteArray()).Replace('/', '_').Replace('+', '-').Substring (0, 22) Convert.ToBase64String(Guid.NewGuid().ToByteArray()).Replace('/', '_').Replace('+', '-').Substring (0, 22)
/// A user's access level
type AccessLevel =
/// The user may create and publish posts and edit the ones they have created
| Author
/// The user may edit posts they did not create, but may not delete them
| Editor
/// The user may delete posts and configure web log settings
| WebLogAdmin
/// The user may manage themes (which affects all web logs for an installation)
| Administrator
/// Functions to support access levels
module AccessLevel =
/// Weightings for access levels
let private weights =
[ Author, 10
Editor, 20
WebLogAdmin, 30
Administrator, 40
]
|> Map.ofList
/// Convert an access level to its string representation
let toString =
function
| Author -> "Author"
| Editor -> "Editor"
| WebLogAdmin -> "WebLogAdmin"
| Administrator -> "Administrator"
/// Parse an access level from its string representation
let parse it =
match it with
| "Author" -> Author
| "Editor" -> Editor
| "WebLogAdmin" -> WebLogAdmin
| "Administrator" -> Administrator
| _ -> invalidOp $"{it} is not a valid access level"
/// Does a given access level allow an action that requires a certain access level?
let hasAccess needed held =
weights[needed] <= weights[held]
/// An identifier for a category /// An identifier for a category
type CategoryId = CategoryId of string type CategoryId = CategoryId of string
@ -607,26 +652,6 @@ module WebLogId =
let create () = WebLogId (newId ()) let create () = WebLogId (newId ())
/// A level of authorization for a given web log
type AuthorizationLevel =
/// <summary>The user may administer all aspects of a web log</summary>
| Administrator
/// <summary>The user is a known user of a web log</summary>
| User
/// Functions to support authorization levels
module AuthorizationLevel =
/// Convert an authorization level to a string
let toString = function Administrator -> "Administrator" | User -> "User"
/// Parse a string into an authorization level
let parse value =
match value with
| "Administrator" -> Administrator
| "User" -> User
| it -> invalidOp $"{it} is not a valid authorization level"
/// An identifier for a web log user /// An identifier for a web log user
type WebLogUserId = WebLogUserId of string type WebLogUserId = WebLogUserId of string

View File

@ -47,7 +47,7 @@ let doLogOn : HttpHandler = fun next ctx -> task {
Claim (ClaimTypes.NameIdentifier, WebLogUserId.toString user.id) Claim (ClaimTypes.NameIdentifier, WebLogUserId.toString user.id)
Claim (ClaimTypes.Name, $"{user.firstName} {user.lastName}") Claim (ClaimTypes.Name, $"{user.firstName} {user.lastName}")
Claim (ClaimTypes.GivenName, user.preferredName) Claim (ClaimTypes.GivenName, user.preferredName)
Claim (ClaimTypes.Role, user.authorizationLevel.ToString ()) Claim (ClaimTypes.Role, AccessLevel.toString user.accessLevel)
} }
let identity = ClaimsIdentity (claims, CookieAuthenticationDefaults.AuthenticationScheme) let identity = ClaimsIdentity (claims, CookieAuthenticationDefaults.AuthenticationScheme)

View File

@ -40,15 +40,15 @@ let private doCreateWebLog (args : string[]) (sp : IServiceProvider) = task {
do! data.WebLogUser.add do! data.WebLogUser.add
{ WebLogUser.empty with { WebLogUser.empty with
id = userId id = userId
webLogId = webLogId webLogId = webLogId
userName = args[3] userName = args[3]
firstName = "Admin" firstName = "Admin"
lastName = "User" lastName = "User"
preferredName = "Admin" preferredName = "Admin"
passwordHash = Handlers.User.hashedPassword args[4] args[3] salt passwordHash = Handlers.User.hashedPassword args[4] args[3] salt
salt = salt salt = salt
authorizationLevel = Administrator accessLevel = Administrator
} }
// Create the default home page // Create the default home page