LMU ☀️ CMSI 662
SECURE SOFTWARE DEVELOPMENT
HOMEWORK #1 PARTIAL ANSWERS
  1. Answers vary. Here is my attempt at short definitions:
    1. Risk: A measure of possible loss
    2. Threat: An adversary or a possible negative event
    3. Defect: A problem with a system
    4. Flaw: An defect in the design
    5. Bug: An defect in the code
    6. Vulnerability: A defect that can be targeted by an attacker
    7. Weakness: An error that can become a vulnerability
    8. Failure: An occurrence of a system not meeting correctness criteria
    9. Exploit: Code or data that does the bad things
    10. Integrity: Immunity from malicious or accidental modification
    11. Authentication: Determining whether one is who they say they are
    12. Authorization: Determining whether one is allowed to do something
  2. Answers vary, as you can pick your own videos.
  3. Here are my summaries. Yours will probably be better.
    Heartbleed
    Heartbleed is a vulnerability introduced into OpenSSL in December 2011 and patched in April 2014. OpenSSL does transport layer security for a massive number of servers on the global Internet, and the memory contents of a running OpenSSL processes may contain some pretty sensitive stuff. One of the services in OpenSSL is the heartbeat, where a program periodically asks OpenSSL, “hey server, if you are still there, repeat [this message] back to me, and it is [this many] bytes long.” The server allocates a buffer of the requested number of bytes somewhere in memory (regardless of what is already there), copies the message into it, then responds with the whole buffer. If you ask OpenSSL to echo your 100 byte message but tell it your message is 65536 long, OpenSSL will give you back your 100 bytes plus 65436 bytes of stuff that may contain keys, passwords, logs, who knows. It’s called Heartbleed because it bleeds (leaks) memory to the attacker during the heartbeat.
    XSS
    Cross-site scripting (XSS) occurs when a webapp writes out HTML containing unsanitized user input to a browser. An attacker can submit executable JavaScript (between <script> and </script>tags) via a web form or API call, and if the victim does not validate or sanitize the input and drops it into a page, the JavaScript code will run. The malicious JavaScript can do things like read the victim's cookies or local storage and send things to the attacker’s server or another server that collects this info.
    Billion Laughs
    The famous billion laughs attack can be used against any server that accepts user input in XML format with a payload containing entity definitions which the server starts parsing without checking to see if those entities are nasty. It is possible with just a few hundred bytes to craft an entity that expands into several gigabytes. Billion-laughs refers to a particular payload that when processed turns into one billion instances of the string lol. That’s at least 3GB, enough to impact the availability of a server as a good chunk of its memory gets filled up. This trick can be used to generate payloads of just about any size, so the generic attack is called Uncontrolled XML Entity Expansion.
    CSRF
    In a Cross-Site Request Forgery (CSRF) attack, the attacker tricks the victim into sending data to a server that the victim is authenticated with, but the payload is controlled by the attacker and causes unintended (and bad) things to happen, such as the user changing their password (to something the attacker might know) or to transfer money to the attacker’s account. The attack works if the user is authenticated only via a cookie or token. The attacker crafts a request with the malicious payload and gets the victim to invoke it, generally by visiting an evil site or clicking on a link or image. The malicious request has the same format as a legal form or API request that the user would normally make. There are many protections a server can employ to keep its users safe from these attacks. (Also, users should be careful what they click on.)
  4. Here’s a JavaScript solution to the Shopping Cart problem. My answer is overkill because I like to use homework answers to introduce new material.

    cart.js
    import { v4 as uuidv4 } from "uuid";
    
    // A possible solution to the JavaScript shopping cart secure software
    // demonstration homework problem. Only certain requirements surrounding
    // validation, immutability, and defensive copying were given in the
    // assignment, with the suggestion that you look for more opportunities
    // for secure programming on your own. For this homework solution, I've
    // gone somewhat overboard on input validation since homework answers can
    // be an opportunity to introduce and learn new things.
    
    // I've also over-commented the code, because it is provided as a homework
    // solution, and you might find the comments useful.
    
    // Many secure practices (especially validation) are aided by keeping checks
    // close to domain objects, so we need more than just a single Shopping Cart
    // class. Validation should happen in constructors and in any method that
    // mutates an object. That said, there are some nice validation utilities
    // that apply to basic objects like numbers and strings, so we'll start with
    // those.
    
    function validate(condition, message) {
      if (!condition) throw new Error(message);
    }
    
    function validateType(value, type) {
      // type should be a constructor function, such as the name of a class
      // you create yourself, or something built-in like String or Number.
      // Because the value you pass in might be null or undefined, we employ
      // the optional chaining operator (?.) which is amazing.
      validate(value?.constructor === type, `Expected type ${type}`);
    }
    
    function validateString(value, { minLength, maxLength }) {
      validateType(value, String);
      validate(value.length >= minLength, `Length must be at least ${minLength}`);
      validate(value.length <= maxLength, `Length must be at most ${maxLength}`);
    }
    
    function validateNumber(value, { minimum, maximum }) {
      validateType(value, Number);
      validate(value >= minimum, `Must be at least ${minimum}`);
      validate(value <= maximum, `Must be at most ${maximum}`);
    }
    
    function validateInteger(value, { minimum, maximum }) {
      validateNumber(value, { minimum, maximum });
      validate(Number.isInteger(value), "Must be an integer");
    }
    
    function validateMatches(value, pattern) {
      validateType(pattern, RegExp);
      validate(pattern.test(value), `Must match pattern ${pattern}`);
    }
    
    // Next, we are going simulate an authentication system. In real
    // life, there would be an authentication server and a whole bunch of
    // really cool mechanisms for generating and validating tokens encoding
    // all sorts of claims and such. But for this homework, we'll just
    // simulate the authentication system with tokens being simple objects
    // without any encryption at all. Ugh. Later in the course, we'll
    // give authentication and authorization a proper treatment. Right now
    // our focus is on secure programming and exposing questions and ideas.
    
    function validateAdmin(token) {
      // Just a simulation!!
      validate(token.admin === true, "Admin role required");
    }
    
    function validateOwner(token, expectedCustomerId) {
      // Just a simulation!!
      validate(token.userId.value === expectedCustomerId.value, "Unauthorized");
    }
    
    // Now the first of our domain objects will be for the items that our
    // store offers. Items have a SKU, description, and price. As a best
    // practice, we will make items immutable, which we do by freezing the
    // object upon construction. Note that we do not have to make any of the
    // properties private, since we want to read them after all. Note how each
    // of the properties are validated in the constructor before we accept
    // them. We are careful to put bounds on all strings and all numbers.
    
    export class Item {
      constructor(sku, description, price) {
        validateType(sku, SKU);
        validateString(description, { minLength: 1, maxLength: 1000 });
        validateNumber(price, { minimum: 0.01, maximum: 999999.99 });
        this.sku = sku;
        this.description = description;
        this.price = price;
        Object.freeze(this);
      }
    }
    
    // Now here is the SKU for each item. Note how SKUs have to have a certain
    // format so we explicitly made a SKU class. In other words: SKUs a not
    // strings! While SKUs are not strings, they do "wrap" strings, meaning their
    // internal representation is a string, which we expose through the property
    // called "code". In addition to requiring a certain format, we also want to
    // ensure that SKUs are immutable, so we freeze them in the constructor.
    
    export class SKU {
      static #PATTERN = /^[A-Z]{3}_[A-Z]{3}_\d{2}$/;
      constructor(code) {
        validateMatches(code, SKU.#PATTERN);
        this.code = code;
        Object.freeze(this);
      }
    }
    
    // Items for the shopping cart must already exist in a catalog. Normally
    // the catalog would be in a database, but here we just simulate the
    // database with an in-memory map. After all, the idea of this exercise
    // is to encourage ideas and discussion around security. Were this a real
    // application, we would be using a real database.
    
    // We want the catalog to be readable by anyone but updatable only by
    // admins. The catalog will be a single object, so hiding the details of
    // the catalog is best done with a closure. We'll use a Map to store the
    // items, keyed by SKU. Note that we are careful to freeze the catalog
    // object itself, but not the items within it. We want the items to be
    // immutable, but we want to be able to add and remove items from the
    // catalog. We'll use a static method to validate that a SKU is in the
    
    // While you might think a catalog is simply a list of items, we're going
    // to implement it as a Map so we can efficiently look up items by SKU.
    // The map itself will be keyed on the strings wrapped by the SKUs. This
    // is because strings are compared by value, not by reference. If we
    // keyed the map on the SKUs themselves, we would have to be careful to
    // always use the same SKU object when looking up items. By keying the
    // map on the string, we can use any SKU object to look up items.
    
    const catalog = (() => {
      const map = new Map();
      const initialImport = [
        ["ABC_DEF_21", "Widget", 10.0],
        ["ZZZ_BOB_77", "Thing", 20.0],
        ["XXX_ROB_77", "Cosa", 15.0],
        ["YYY_JIL_77", "Stuff", 200.0],
        ["WWW_BIL_77", "Cool stuff", 0.1],
      ];
      for (const [code, description, price] of initialImport) {
        map.set(code, new Item(new SKU(code), description, price));
      }
      return Object.freeze({
        upsertItem(token, item) {
          validateAdmin(token);
          validateType(item, Item);
          map.set(item.sku.code, item);
        },
        removeItem(token, sku) {
          validateAdmin(token);
          validateType(sku, SKU);
          map.delete(sku.code);
        },
        fetchItem(sku) {
          validateType(sku, SKU);
          return map.get(sku.code);
        },
        validateHasItem(sku) {
          validateType(sku, SKU);
          validate(map.has(sku.code), "Invalid SKU");
        },
      });
    })();
    
    // The quantity of each item in the cart can not be zero or negative, and
    // it can not be more than some reasonable upper bound. We make a Quantity
    // class because classes are not "just numbers." Also, because immutability
    // is a best practice, we make quantities immutable.
    
    export class Quantity {
      constructor(value) {
        validateInteger(value, { minimum: 1, maximum: 500 });
        this.value = value;
        Object.freeze(this);
      }
    }
    
    // Now in the store front specification for this assignment, we are required
    // to implement customer ids to have a certain format. Therefore, customer
    // ids are not strings! They are a special class of their own, with custom
    // validation inside the constructor. We also want customer ids to be
    // immutable, so we freeze them in the constructor.
    
    export class CustomerId {
      // Best to hold validation specifications within the class as private,
      // class-wide properties. This makes it easy to see them as part of the
      // class's specification.
      static #PATTERN = /^\p{L}{3}\d{5}\p{L}{2}-[AQ]$/u;
    
      constructor(value) {
        validateString(value, { minLength: 12, maxLength: 12 });
        validateMatches(value, CustomerId.#PATTERN);
        this.value = value;
        Object.freeze(this);
      }
    
      toString() {
        return "<CustomerId: " + this.value + ">";
      }
    }
    
    // The actual shopping cart belongs to a single user and contains several items
    // mapping each item to a quantity. Because the cart id and the customer id
    // are immutable, they do not have to be defensively copied and can be directly
    // accessed. The internal mapping (sku->quantity), however, can only be changed
    // carefully through methods, and information about the cart contents is only
    // obtained by making a (defensive) copy of the mapping.
    
    // The cart object is frozen on construction so the owner cannot change and the
    // identity of the map object cannot change. But since maps in JavaScript are
    // mutable, we can still add and remove items.
    
    export class Cart {
      // The card id and the owner's customer id do NOT have to be private
      // because we will freeze the object. However, simply freezing the object
      // does NOT make properties that are objects immutable. Therefore, it is
      // imperative that we make the map private so that we can control access
      // to it. We will make the map immutable by using a getter that returns
      // a defensive copy of the map.
      #items;
    
      constructor(token) {
        this.id = uuidv4();
        validateType(token.userId, CustomerId);
        this.customerId = token.userId;
        this.#items = new Map();
        Object.freeze(this);
      }
    
      fetchItems(token) {
        validateOwner(token, this.customerId);
        // Defensive (deep!) copy of the map: gotta love structuredClone.
        return structuredClone(this.#items);
      }
    
      addItems(token, sku, quantity) {
        validateOwner(token, this.customerId);
        validateType(sku, SKU);
        validateType(quantity, Quantity);
        catalog.validateHasItem(sku);
        // At this point we might consider doing an inventory check to make
        // sure there are at least the desired number of units in the inventory.
        // But this is beyond the scope of the current assignment, so we're not
        // going to bother with that here. There are a bunch of additional issues
        // around doing these kinds of checks in a distributed system, anyway,
        // so it is fine to leave it for later.
        this.#items.set(
          sku.code,
          (this.#items.get(sku.code) ?? 0) + quantity.value
        );
      }
    
      removeItem(token, sku) {
        validateOwner(token, this.customerId);
        validateType(sku, SKU);
        this.#items.delete(sku.code);
      }
    
      updateItemQuantity(token, sku, quantity) {
        validateOwner(token, this.customerId);
        validateType(sku, SKU);
        validateType(quantity, Quantity);
        this.#items.set(sku.code, quantity.value);
      }
    
      totalCost(token) {
        validateOwner(token, this.customerId);
        let total = 0;
        for (const [sku, quantity] of this.#items.entries()) {
          const item = catalog.fetchItem(new SKU(sku));
          total += item.price * quantity;
        }
        return total;
      }
    }
    

    Now we haven’t learned how to do unit testing yet, but it’s a good time to introduce it. (I used mocha):

    cart.test.js
    import assert from 'assert/strict'
    import { SKU, Item, CustomerId, Quantity, Cart } from '../cart.js'
    
    describe('The Shopping Cart Module', () => {
    
      it('builds and validates skus', () => {
        assert.throws(() => new SKU(null), "cannot be null");
        assert.throws(() => new SKU(5), "cannot be a number");
        assert.throws(() => new SKU("3333"), "too short");
        assert.throws(() => new SKU("hello".repeat(100)), "too long");
        assert.throws(() => new SKU("ABC-DEF-89"), "malformed");    
        const sku = new SKU("ABC_DEF_89");
        assert.deepEqual(sku.code, "ABC_DEF_89", "can read code");
        assert.throws(() => sku.code = "ABC_DEF_90", "is immutable");
      })
    
      it('builds and validates quantities', () => {
        assert.throws(() => new Quantity(null), "cannot be null");
        assert.throws(() => new Quantity(true), "cannot be a boolean");
        assert.throws(() => new Quantity("7"), "cannot be a string");
        assert.throws(() => new Quantity(0), "cannot be less than 1");
        assert.throws(() => new Quantity(501), "cannot be greater than 500");
        assert.throws(() => new Quantity(10.0000001), "cannot be non-integer");
        assert.ok(() => new Quantity(1), "can be lowest value");
        assert.ok(() => new Quantity(500), "can be highest value");
        assert.ok(() => new Quantity(99), "can be any value in between");
        const five = new Quantity(5);
        assert.deepEqual(five.value, 5, "value is readable");
        assert.throws(() => five.value = 3, "is immutable");
      })
    
      it('builds and validates items', () => {
        const sku = new SKU("ABC_DEF_21");
        assert.throws(() => new Item(1, 2, 3), "sku must be SKU");
        assert.throws(() => new Item(sku, "Oreos", 0), "quantity must be Quantity");
        assert.throws(() => new Item(sku, "", 3), "description cannot be empty");
        const item = new Item(new SKU("ABC_DEF_21"), "Turmeric", 3.25);
        assert.deepEqual(item.sku, sku, "can read sku");
        assert.deepEqual(item.description, "Turmeric", "can read description");
        assert.deepEqual(item.price, 3.25, "can read price");
        assert.throws(() => item.sku = new SKU("ABC_DEF_22"), "is immutable");
      })
    
      it('builds and validates customer ids', () => {
        assert.throws(() => new CustomerId(5), "cannot be a number");
        assert.throws(() => new CustomerId("3333"), "too short");
        assert.throws(() => new CustomerId("hello".repeat(100)), "too long");
        assert.throws(() => new CustomerId("abc7309Zdo-Q"), "malformed");
        const u1 = new CustomerId("abc94788yq-A");
        const u2 = new CustomerId("abc94788yq-A");
        assert.deepEqual(u1, u2, "same id is equal");
        assert.deepEqual(u1.toString(), "<CustomerId: abc94788yq-A>", "toString works");
        assert.throws(() => u1.id = "abc94788yq-B", "is immutable");
      })
    
      it('validates cart parameters', () => {
        const token = { userId: new CustomerId("XYZ12345AB-Q") };
        assert.throws(() => new Cart(null), "token cannot be null");
        assert.throws(() => new Cart({}), "token has no userId");
        assert.throws(() => new Cart({userId: "userX"}), "token has malformed userId");
        assert.ok(new Cart(token), "a good token");
      })
    
      it('disallows non-owner access', () => {
        const shopper = { userId: new CustomerId("XOX01010XO-A") };
        const hacker = { userId: new CustomerId("HAC66666KE-Q") };
    
        const c = new Cart(shopper);
        const sku = new SKU("ABC_DEF_21");
        const five = new Quantity(5);
        assert.throws(() => c.addItems(hacker, sku, five), "hacker cannot add");
        assert.throws(() => c.removeItem(hacker, sku), "hacker cannot remove");
        assert.throws(() => c.updateItemQuantity(hacker, sku, five), "hacker cannot update");
        assert.throws(() => c.fetchItems(hacker), "hacker cannot fetch");
        assert.throws(() => c.totalCost(hacker), "hacker cannot total");
      })
    
      it('can add and fetch items', () => {
        const shopper = { userId: new CustomerId("XOX01010XO-A") };
        const c = new Cart(shopper);
        const widgetSku = new SKU("ABC_DEF_21");
        const cosaSku = new SKU("XXX_ROB_77");
        c.addItems(shopper, widgetSku, new Quantity(3));
        c.addItems(shopper, cosaSku, new Quantity(2));
        const fetchedItems = c.fetchItems(shopper)
        const expectedItems = new Map([
          [widgetSku.code, 3],
          [cosaSku.code, 2],
        ])
        assert.deepEqual(fetchedItems, expectedItems)
      })
    
      it('properly defensively copies items on fetch', () => {
        const shopper = { userId: new CustomerId("XOX01010XO-A") };
        const c = new Cart(shopper);
        const widgetSku = new SKU("ABC_DEF_21");
        c.addItems(shopper, widgetSku, new Quantity(3));
        const fetchedItems = c.fetchItems(shopper)
        const expectedItems = new Map([
          [widgetSku.code, 3],
        ])
        assert.deepEqual(fetchedItems, expectedItems)
        // Make sure changing the result of fetchItems does not change
        // the cart itself through a back door
        fetchedItems.set(widgetSku.code, 100);
        assert.deepEqual(c.fetchItems(shopper), expectedItems)
      })
    
      it('can remove, update, and total items', () => {
        const shopper = { userId: new CustomerId("XOX01010XO-A") };
        const c = new Cart(shopper);
        const widgetSku = new SKU("ABC_DEF_21");
        const cosaSku = new SKU("XXX_ROB_77");
        c.addItems(shopper, widgetSku, new Quantity(3));
        assert.deepEqual(c.totalCost(shopper), 3 * 10.00);
        c.addItems(shopper, cosaSku, new Quantity(2));
        assert.deepEqual(c.totalCost(shopper), 3 * 10.00 + 2 * 15.00);
        c.removeItem(shopper, widgetSku);
        c.updateItemQuantity(shopper, cosaSku, new Quantity(5));
        assert.deepEqual(c.totalCost(shopper), 5 * 15.00);
      })
    })
    

    SUPER BOUNS FUN TIME

    Here’s a Java solution to the Shopping Cart problem. Again, my answer is overkill because I like to use homework answers to introduce new material, and to over-comment to explain how the solutions tie in to the learning objectives.

    Cart.java
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Objects;
    import java.util.UUID;
    
    // A possible solution to the Java shopping cart secure software
    // demonstration homework problem. Only certain requirements surrounding
    // validation, immutability, and defensive copying were given in the
    // assignment, with the suggestion that you look for more opportunities
    // for secure programming on your own. For this homework solution, I've
    // gone somewhat overboard on input validation since homework answers can
    // be an opportunity to introduce and learn new things.
    
    // I've also over-commented the code, because it is provided as a homework
    // solution, and you might find the comments useful.
    
    // Many secure practices (especially validation) are aided by keeping checks
    // close to domain objects, so we need more than just a single Shopping Cart
    // class. Validation should happen in constructors and in any method that
    // mutates an object. That said, there are some nice validation utilities
    // that apply to basic objects like numbers and strings, so we'll start with
    // those.
    
    interface Validate {
        static void ok(boolean condition, String message) {
            if (!condition) {
                throw new IllegalArgumentException(message);
            }
        }
    
        static void notNull(Object value) {
            // This check is built into Java but we are supplying a pass-through
            // method for consistency with the other validation methods.
            Objects.requireNonNull(value, "Must not be null");
        }
    
        static void stringLength(String value, int minLength, int maxLength) {
            ok(value.length() >= minLength, "Length must be at least " + minLength);
            ok(value.length() <= maxLength, "Length must be at most " + maxLength);
        }
    
        static void doubleRange(double value, double minimum, double maximum) {
            ok(value >= minimum, "Must be at least " + minimum);
            ok(value <= maximum, "Must be at most " + maximum);
        }
    
        static void integerRange(int value, int minimum, int maximum) {
            ok(value >= minimum, "Must be at least " + minimum);
            ok(value <= maximum, "Must be at most " + maximum);
        }
    
        static void matches(String value, String pattern) {
            ok(value.matches(pattern), "Must match pattern " + pattern);
        }
    }
    
    // Next, we are going simulate an authentication system. In real
    // life, there would be an authentication server and a whole bunch of
    // really cool mechanisms for generating and validating tokens encoding
    // all sorts of claims and such. But for this homework, we'll just
    // simulate the authentication system with tokens being simple objects
    // without any encryption at all. Ugh. Later in the course, we'll
    // give authentication and authorization a proper treatment. Right now
    // our focus is on secure programming and exposing questions and ideas.
    
    record Token(CustomerId customerId, boolean admin) {
        public Token {
            Validate.notNull(customerId);
        }
    
        void validateIsAdmin() {
            // Just a simulation!!
            Validate.ok(this.admin, "Admin role required");
        }
    
        void validateIsOwner(CustomerId expectedId) {
            // Just a simulation!!
            Validate.ok(this.customerId.equals(expectedId), "Unauthorized");
        }
    }
    
    // Now the first of our domain objects will be for the items that our
    // store offers. Items have a Sku, description, and price. As a best
    // practice, we will make items immutable, which we do by using a Java
    // record. Note how each of the properties are validated in the
    // constructor, with bounds on all strings and all numbers.
    
    record Item(Sku sku, String description, double price) {
        public Item {
            Validate.notNull(sku);
            Validate.notNull(description);
            Validate.stringLength(description, 1, 1000);
            Validate.doubleRange(price, 0.01, 999999.99);
        }
    }
    
    // Now here is the Sku for each item. Note how Skus have to have a certain
    // format so we explicitly made a Sku class. In other words: Skus a not
    // strings! While Skus are not strings, they do "wrap" strings, meaning their
    // internal representation is a string, which we expose through the property
    // called "code". In addition to requiring a certain format, we also want to
    // ensure that Skus are immutable, so we freeze them in the constructor.
    
    record Sku(String code) {
        public Sku {
            Validate.notNull(code);
            Validate.matches(code, "^[A-Z]{3}_[A-Z]{3}_\\d{2}$");
        }
    }
    
    // Items for the shopping cart must already exist in a catalog. Normally
    // the catalog would be in a database, but here we just simulate the
    // database with an in-memory map. After all, the idea of this exercise
    // is to encourage ideas and discussion around security. Were this a real
    // application, we would be using a real database.
    
    // We want the catalog to be readable by anyone but updatable only by
    // admins. The catalog will be a single object, so hiding the details of
    // the catalog is best done with a closure. We'll use a Map to store the
    // items, keyed by Sku. Note that we are careful to freeze the catalog
    // object itself, but not the items within it. We want the items to be
    // immutable, but we want to be able to add and remove items from the
    // catalog. We'll use a static method to validate that a Sku is in the
    
    // While you might think a catalog is simply a list of items, we're going
    // to implement it as a Map so we can efficiently look up items by Sku.
    // The map itself will be keyed on the strings wrapped by the Skus. This
    // is because strings are compared by value, not by reference. If we
    // keyed the map on the Skus themselves, we would have to be careful to
    // always use the same Sku object when looking up items. By keying the
    // map on the string, we can use any Sku object to look up items.
    
    class Catalog {
        private static Map<String, Item> initialImport = Map.of("ABC_DEF_21",
                new Item(new Sku("ABC_DEF_21"), "Widget", 10.0), "ZZZ_BOB_77",
                new Item(new Sku("ZZZ_BOB_77"), "Thing", 20.0), "XXX_ROB_77",
                new Item(new Sku("XXX_ROB_77"), "Cosa", 15.0), "YYY_JIL_77",
                new Item(new Sku("YYY_JIL_77"), "Stuff", 200.0), "WWW_BIL_77",
                new Item(new Sku("WWW_BIL_77"), "Cool stuff", 0.1));
    
        private static Map<String, Item> map = new HashMap<>(initialImport);
    
        private Catalog() {
            // Prevents instantiation
        }
    
        public static void upsertItem(Token token, Item item) {
            token.validateIsAdmin();
            map.put(item.sku().code(), item);
        }
    
        public static void removeItem(Token token, Sku sku) {
            token.validateIsAdmin();
            map.remove(sku.code());
        }
    
        public static Item fetchItem(Sku sku) {
            return map.get(sku.code());
        }
    
        public static void validateHasItem(Sku sku) {
            Validate.ok(map.containsKey(sku.code()), "Invalid Sku");
        }
    }
    
    // The quantity of each item in the cart can not be zero or negative, and
    // it can not be more than some reasonable upper bound. We make a Quantity
    // class because classes are not "just numbers." Also, because immutability
    // is a best practice, we make quantities immutable.
    
    record Quantity(int value) {
        public Quantity {
            Validate.integerRange(value, 1, 500);
        }
    }
    
    // Now in the store front specification for this assignment, we are required
    // to implement customer ids to have a certain format. Therefore, customer
    // ids are not strings! They are a special class of their own, with custom
    // validation inside the constructor. We also want customer ids to be
    // immutable, so we freeze them in the constructor.
    
    record CustomerId(String lexeme) {
        // Best to hold validation specifications within the class as private,
        // class-wide properties. This makes it easy to see them as part of th
        // class's specification. Here the length validation, while not strictly
        // necessary, is interesting to include for the purposes of showing multi-
        // layered validations in a constructor.
        private static final int USER_ID_LENGTH = 12;
        private static final String USER_ID_PATTERN = "^\\p{L}{3}\\d{5}\\p{L}{2}-[AQ]$";
    
        public CustomerId {
            Validate.notNull(lexeme);
            // The length check is quick, so done before the regex check
            Validate.stringLength(lexeme, USER_ID_LENGTH, USER_ID_LENGTH);
            Validate.matches(lexeme, USER_ID_PATTERN);
        }
    
        @Override
        public String toString() {
            return "<CustomerId: " + this.lexeme + ">";
        }
    }
    
    // The actual shopping cart belongs to a single user and contains several items
    // mapping each item to a quantity. Because the cart id and the customer id
    // are immutable, they do not have to be defensively copied and can be directly
    // accessed. The internal mapping (sku->quantity), however, can only be changed
    // carefully through methods, and information about the cart contents is only
    // obtained by making a (defensive) copy of the mapping.
    
    // The cart object is frozen on construction so the owner cannot change and the
    // identity of the map object cannot change. But since maps in Java are
    // mutable, we can still add and remove items.
    
    class Cart {
        private final UUID id;
        private final CustomerId customerId;
        private final Map<String, Integer> items;
    
        public Cart(Token token) {
            this.id = UUID.randomUUID();
            this.customerId = token.customerId();
            this.items = new HashMap<>();
        }
    
        public Map<String, Integer> fetchItems(Token token) {
            token.validateIsOwner(this.customerId);
            // Java does not have a built-in way to make a deep copy of a map.
            // But in this case, the map only contains immutable values, so we
            // can get away with a shallow copy!
            return Map.copyOf(items);
        }
    
        public void addItems(Token token, Sku sku, Quantity quantity) {
            token.validateIsOwner(this.customerId);
            Catalog.validateHasItem(sku);
            // At this point we might consider doing an inventory check to make
            // sure there are at least the desired number of units in the inventory.
            // But this is beyond the scope of the current assignment, so we're not
            // going to bother with that here. There are a bunch of additional issues
            // around doing these kinds of checks in a distributed system, anyway,
            // so it is fine to leave it for later.
            this.items.put(
                    sku.code(),
                    this.items.getOrDefault(sku.code(), 0) + quantity.value());
        }
    
        public void removeItem(Token token, Sku sku) {
            token.validateIsOwner(this.customerId);
            this.items.remove(sku.code());
        }
    
        public void updateItemQuantity(Token token, Sku sku, Quantity quantity) {
            token.validateIsOwner(this.customerId);
            this.items.put(sku.code(), quantity.value());
        }
    
        public double totalCost(Token token) {
            token.validateIsOwner(this.customerId);
            var total = 0.0;
            for (var e : this.items.entrySet()) {
                total += Catalog.fetchItem(new Sku(e.getKey())).price()
                        * e.getValue();
            }
            return total;
        }
    }
    

    And a test:

    CartTest.java
    import java.util.Map;
    import org.junit.jupiter.api.Test;
    
    import static org.junit.Assert.assertNotNull;
    import static org.junit.jupiter.api.Assertions.assertEquals;
    import static org.junit.jupiter.api.Assertions.assertThrows;
    
    public class CartTest {
    
        @Test
        public void testBuildsAndValidatesSkus() {
            assertThrows(RuntimeException.class, () -> new Sku(null));
            assertThrows(RuntimeException.class, () -> new Sku("3333"));
            assertThrows(RuntimeException.class, () -> new Sku("hello".repeat(100)));
            assertThrows(RuntimeException.class, () -> new Sku("ABC-DEF-89"));
            var sku = new Sku("ABC_DEF_89");
            assertEquals(sku.code(), "ABC_DEF_89");
            assertEquals(sku.toString(), "Sku[code=ABC_DEF_89]");
        }
    
        @Test
        public void testBuildsAndValidatesQuantities() {
            assertThrows(RuntimeException.class, () -> new Quantity(0));
            assertThrows(RuntimeException.class, () -> new Quantity(501));
            assertThrows(RuntimeException.class, () -> new Quantity(-1));
            assertNotNull(new Quantity(1));
            assertNotNull(new Quantity(500));
            var q1 = new Quantity(5);
            var q2 = new Quantity(5);
            assertEquals(q1.value(), 5);
            assertEquals(q1, q2);
            assertEquals(q1.toString(), "Quantity[value=5]");
        }
    
        @Test
        public void textBuildsAndValidatesItems() {
            assertThrows(RuntimeException.class, () -> new Sku(null));
            var sku = new Sku("ABC_DEF_21");
            assertThrows(RuntimeException.class, () -> new Item(sku, "", 3));
            var item = new Item(new Sku("ABC_DEF_21"), "Turmeric", 3.25);
            assertEquals(item.sku(), sku);
            assertEquals(item.description(), "Turmeric");
            assertEquals(item.price(), 3.25);
        }
    
        @Test
        public void testBuildsAndValidatesCustomerIds() {
            assertThrows(RuntimeException.class, () -> new CustomerId(null));
            assertThrows(RuntimeException.class, () -> new CustomerId("3333"));
            assertThrows(RuntimeException.class, () -> new CustomerId("hello".repeat(100)));
            assertThrows(RuntimeException.class, () -> new CustomerId("abc7309Zdo-Q"));
            var u1 = new CustomerId("abc94788yq-A");
            var u2 = new CustomerId("abc94788yq-A");
            assertEquals(u1, u2);
            assertEquals(u1.toString(), "<CustomerId: abc94788yq-A>");
        }
    
        @Test
        public void testValidatesCartParameters() {
            assertThrows(RuntimeException.class, () -> new Cart(null));
            var token = new Token(new CustomerId("abc94788yq-A"), false);
            assertNotNull(new Cart(token));
        }
    
        @Test
        public void testNonOwnerAccessIsDisallowed() {
            var shopper = new Token(new CustomerId("XOX01010XO-A"), false);
            var hacker = new Token(new CustomerId("HAC66666KE-Q"), false);
            var c = new Cart(shopper);
            var sku = new Sku("ABC_DEF_21");
            var five = new Quantity(5);
            assertThrows(RuntimeException.class, () -> c.addItems(hacker, sku, five));
            assertThrows(RuntimeException.class, () -> c.removeItem(hacker, sku));
            assertThrows(RuntimeException.class,
                    () -> c.updateItemQuantity(hacker, sku, five));
            assertThrows(RuntimeException.class, () -> c.fetchItems(hacker));
            assertThrows(RuntimeException.class, () -> c.totalCost(hacker));
        }
    
        @Test
        public void testCanAddAndFetchItems() {
            var shopper = new Token(new CustomerId("XOX01010XO-A"), false);
            var c = new Cart(shopper);
            var widgetSku = new Sku("ABC_DEF_21");
            var cosaSku = new Sku("XXX_ROB_77");
            c.addItems(shopper, widgetSku, new Quantity(3));
            c.addItems(shopper, cosaSku, new Quantity(2));
            var fetchedItems = c.fetchItems(shopper);
            var expectedItems = Map.of(widgetSku.code(), 3, cosaSku.code(), 2);
            assertEquals(fetchedItems, expectedItems);
        }
    
        @Test
        public void testDefensivelyCopiesItemsOnFetch() {
            var shopper = new Token(new CustomerId("XOX01010XO-A"), false);
            var c = new Cart(shopper);
            var widgetSku = new Sku("ABC_DEF_21");
            c.addItems(shopper, widgetSku, new Quantity(3));
            var fetchedItems = c.fetchItems(shopper);
            var expectedItems = Map.of(widgetSku.code(), 3);
            assertEquals(fetchedItems, expectedItems);
            // Make sure changing the result of fetchItems does not change the
            // cart itself through a back door. Java actually throws an exception
            // (UnsupportedOperationException) here because we actually returned
            assertThrows(UnsupportedOperationException.class,
                    () -> fetchedItems.put(widgetSku.code(), 100));
        }
    
        @Test
        public void testCanAddRemoveUpdateAndTotal() {
            var shopper = new Token(new CustomerId("XOX01010XO-A"), false);
            var c = new Cart(shopper);
            var widgetSku = new Sku("ABC_DEF_21");
            var cosaSku = new Sku("XXX_ROB_77");
            c.addItems(shopper, widgetSku, new Quantity(3));
            assertEquals(c.totalCost(shopper), 3 * 10.00);
            c.addItems(shopper, cosaSku, new Quantity(2));
            assertEquals(c.totalCost(shopper), 3 * 10.00 + 2 * 15.00);
            c.removeItem(shopper, widgetSku);
            c.updateItemQuantity(shopper, cosaSku, new Quantity(5));
            assertEquals(c.totalCost(shopper), 5 * 15.00);
        }
    
    }