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

@ -300,7 +300,7 @@ module Map =
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
@ -103,7 +103,7 @@ type SQLiteWebLogUserData (conn : SqliteConnection) =
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

@ -174,7 +174,7 @@ type SQLiteData (conn : SqliteConnection, log : ILogger<SQLiteData>) =
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
@ -454,7 +454,7 @@ module WebLogUser =
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

@ -48,7 +48,7 @@ let private doCreateWebLog (args : string[]) (sp : IServiceProvider) = task {
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