Skip to content

Instantly share code, notes, and snippets.

@jcdyer
Created February 14, 2020 15:04
Show Gist options
  • Save jcdyer/4c451b925786ba535d1c611cbe1bc8e5 to your computer and use it in GitHub Desktop.
Save jcdyer/4c451b925786ba535d1c611cbe1bc8e5 to your computer and use it in GitHub Desktop.
Type-enforced permission system
use gs1::Gtin;
pub mod owned {
use gs1::Gtin;
// This is constructed as a newtype wrapper around Gtin that only exists
// to prove that the GTIN belongs to the currently logged-in user.
#[derive(Debug, Clone)]
pub struct OwnedGtin(Gtin);
impl OwnedGtin {
/// Construct an OwnedGtin from a Gtin and an AuthenticatedUser. The
/// OwnedGtin can only be returned if the user owns a CompanyPrefix
/// that matches the Gtin.
///
/// This is the base constructor, which validates the user's claim to
/// the GTIN. Most other constructors should delegate to this.
pub fn from_gtin(user: &super::auth::AuthenticatedUser, gtin: gs1::Gtin) -> Option<OwnedGtin> {
let prefix = user.prefix()?;
if format!("{}", gtin)[1..1+prefix.len()] == format!("{}", prefix)[..] {
Some(OwnedGtin(gtin))
} else {
None
}
}
/// Construct an OwnedGtin from a string and an AuthenticatedUser.
pub fn from_str(user: &super::auth::AuthenticatedUser, value: &str) -> Option<OwnedGtin> {
let gtin = Gtin::new(String::from(value)).ok()?;
OwnedGtin::from_gtin(user, gtin)
}
/// Construct an OwnedGtin from an AuthenticatedUser, an indicator
/// digit, and item reference number.
pub fn from_parts(user: &super::auth::AuthenticatedUser, indicator_digit: char, item_ref: u64) -> Option<OwnedGtin> {
let prefix = user.prefix()?;
let gtin = Gtin::from_parts(prefix, indicator_digit, item_ref).ok()?;
OwnedGtin::from_gtin(user, gtin)
}
/// Get an OwnedGtin that is related to an existing OwnedGtin.
///
/// The new OwnedGtin will only differ from the original by the
/// indicator digit and the checkdigit.
pub fn get_related(&self, indicator_digit: char) -> Option<OwnedGtin> {
// This relies on the validation of the original GTIN to ensure the
// validation of the new GTIN
let new = self.0.get_related(indicator_digit).ok()?;
Some(OwnedGtin(new))
}
/// Extract the gs1::Gtin from an OwnedGtin.
///
/// This consumes the OwnedGtin.
pub fn into_inner(self) -> Gtin {
self.0
}
}
}
pub mod auth {
#[derive(Debug)]
pub struct AuthenticatedUser {
id: String,
prefix: Option<gs1::CompanyPrefix>,
}
impl AuthenticatedUser {
pub fn id(&self) -> &str {
&self.id
}
pub fn prefix(&self) -> Option<&gs1::CompanyPrefix> {
self.prefix.as_ref()
}
}
/// Log a user in with a username and password.
///
/// This is the only way to get an AuthenticatedUser outside the auth
/// module. In a real app, login would have happened in a separate
/// context, and we would construct the AuthenticatedUser from an HTTP
/// request, and the implementation might check a session variable or
/// validate the signature of a JWT.
pub fn login(id: &str, password: &str) -> Option<AuthenticatedUser> {
match (id, password) {
("coke", "is it") => Some(AuthenticatedUser {
id: String::from(id),
prefix: Some(gs1::CompanyPrefix::new("0123".into()).unwrap()),
}),
("ipc", "12345") => Some(AuthenticatedUser {
id: String::from(id),
prefix: Some(gs1::CompanyPrefix::new("10835298".into()).unwrap()),
}),
("pomnmop", "password") => Some(AuthenticatedUser {
id: String::from(id),
prefix: None,
}),
_ => None,
}
}
}
#[derive(Debug)]
struct Product {
name: String,
gtin: Gtin,
}
/// Using a GTIN known to be owned by the current user, create a product.
///
/// Note that the product struct holds an unowned GTIN. That struct may be
/// accessed by users that don't own the GTIN, but those users should not
/// be able to create a new one.
///
/// In a complete app, we would use OwnedGtins to protect access to most
/// methods that insert or update product records in the database, but GET
/// requests would use a plain gs1::Gtin.
fn create_product(name: &str, gtin: owned::OwnedGtin) -> Product {
Product {
name: name.to_owned(),
gtin: gtin.into_inner(),
}
}
fn main() {
let businessname = "ipc";
let password = "12345";
println!("logging in as {}/{}", businessname, password);
let logged_in_user = auth::login(businessname, password).expect("login failed");
println!("{:?}", logged_in_user);
print!("Constructing valid owned GTIN: ");
let owned_gtin = owned::OwnedGtin::from_str(
&logged_in_user,
"01083529887658",
).expect("Not an owned GTIN");
println!("ok");
print!("Checking that constructing unowned GTIN is not allowed: ");
assert!(owned::OwnedGtin::from_str(
&logged_in_user,
"08283349887658",
).is_none()); // Cannot create a gtin that I don't own.
println!("ok");
// No failure possible here. The gtin is already checked, and confirmed valid
let product = create_product("Multirye bread", owned_gtin);
println!("Product: {:?}", product);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment