Skip to main content

Using an Array of Objects in C++

 I've been programming for years (over 35 at this point, which is crazy to think about). My career right now is much more Software Architecture, and much less Software Developer, but I still get some time to write out GraphQL APIs in TypeScript, Vue 3 UIs, GitLab pipelines, and just generally making "big" decisions and helping make them a reality.

It's nice every now and then to come across different articles and ideas that get me to remember life in college when I was using C++. Who would have thought C++ was the "hot new thing" right now (though I suppose it's more like Rust and Go, both great languages as well).

One of the things I find frustrating with most technical posts is where they focus on the "how do I build an app" and not so much on "how do I do this one slightly useful thing". I figured I'd throw one together what was front of mind, using user attributes for permissions (i.e., Attribute Based Access Control - ABAC) to check permissions.

Our classes look like the following.


// Attribute defines a user's attributes (often also thought of as permissions)
class Attribute
{
private:
    string id;
    string name;

public:
    Attribute(string id, string name)
    {
        this->id = id;
        this->name = name;
    }

    string getId()
    {
        return id;
    }

    string getName()
    {
        return name;
    }
};

// AttributeGroup defines a group of attributes (often also thought of as a role)
class AttributeGroup
{
private:
    string id;
    string name;
    // Use a vector since we don't know how many attributes will be added
    vector attributes;

public:
    AttributeGroup(string id, string name)
    {
        this->id = id;
        this->name = name;
    }

    string getId()
    {
        return id;
    }

    string getName()
    {
        return name;
    }

    void addAttribute(Attribute attribute)
    {
        attributes.push_back(attribute);
    }

    vector getAttributes()
    {
        return attributes;
    }
};

// U.S. Mailing Address information
class Address
{
private:
    string street1;
    string street2;
    string city;
    string state;
    string postalCode;
    bool international;

public:
    Address()
    {
        this->street1 = "";
        this->street2 = "";
        this->city = "";
        this->state = "";
        this->postalCode = "";
        this->international = false;
    };

    Address(string street1, string street2, string city, string state, string postalCode, bool international = false)
    {
        this->street1 = street1;
        this->street2 = street2;
        this->city = city;
        this->state = state;
        this->postalCode = postalCode;
        this->international = international;
    };

    // getAddress returns a formatted address natching the U.S. or international standard.
    string getAddress()
    {
        if (international)
            return street1.append("\n").append(street2).append("\n").append(city).append(" ").append(postalCode);

        return street1 + "\n" + street2 + "\n" + city + ", " + state + " " + postalCode;
    };

    void setAddress(string street1, string street2, string city, string state, string postalCode, bool international = false)
    {
        this->street1 = street1;
        this->street2 = street2;
        this->city = city;
        this->state = state;
        this->postalCode = postalCode;
        this->international = international;
    };
};

class User
{
private:
    string id;
    string name;
    Address address;
    // Use a vector since we don't know how many attributes will be associated with the user
    vector attributes;
    // Use a vector since we don't know how many groups will be associated with the user
    vector attributeGroups;

public:
    User()
    {
        this->id = "";
        this->name = "";
        this->address = Address();
        this->attributes = vector();
        this->attributeGroups = vector();
    };

    User(string id, string name, Address address)
    {
        this->id = id;
        this->name = name;
        this->address = address;
        this->attributes = vector();
        this->attributeGroups = vector();
    };

    User(string id, string name, Address address, vector attributeGroups)
    {
        this->id = id;
        this->name = name;
        this->address = address;
        this->attributes = vector();
        this->attributeGroups = vector();

        for (AttributeGroup attributeGroup : attributeGroups)
        {
            addAttributeGroup(attributeGroup);
        };
    };

    User(string id, string name, Address address, vector attributes)
    {
        this->id = id;
        this->name = name;
        this->address = address;
        this->attributes = attributes;
        this->attributeGroups = vector();
    };

    User(string id, string name, Address address, vector attributeGroups, vector attributes)
    {
        this->id = id;
        this->name = name;
        this->address = address;
        this->attributes = attributes;

        for (AttributeGroup attributeGroup : attributeGroups)
        {
            addAttributeGroup(attributeGroup);
        };
    };

    string getId()
    {
        return this->id;
    }

    string getName()
    {
        return this->name;
    }

    Address getAddress()
    {
        return this->address;
    }

    void addAttribute(Attribute attribute)
    {
        this->attributes.push_back(attribute);
    };

    /**
     * addAttributeGroup adds an attribute group to a user, and assigns the permissions to the user's attributes.
     * attributeGroup is the grouping of attributes to add to the user.
     */
    void addAttributeGroup(AttributeGroup attributeGroup)
    {
        this->attributeGroups.push_back(attributeGroup);
        for (Attribute attribute : attributeGroup.getAttributes())
        {
            this->attributes.push_back(attribute);
        };
    };

    /**
     * getAttributeGroups returns all attribute groups assigned to the user.
     */
    vector getAttributeGroups()
    {
        return this->attributeGroups;
    };

    /**
     * getAttributes returns all attributes assigned to the user.
     */
    vector getAttributes()
    {
        return this->attributes;
    };

    /**
     * hasAttribute checks if a user has a specific attribute.
     * attribute is the attribute to check for, matching on their id or name.
     */
    bool hasAttribute(string attribute)
    {
        // If the attribute is entirely empty, then return false since the user must not have it.
        if (attribute == "" && attribute == "")
        {
            return false;
        };

        for (Attribute att : attributes)
        {
            if (att.getId() == attribute || att.getName() == attribute)
            {
                return true;
            };
        };
        return false;
    };
};

The actual function to create some dummy users and then use the attributes (permissions) of the logged in user (here the `runningUser`) to see if the person running the program has permission to retrieve the groups of the user.


// Set a fixed array length to make accessing users more predictable.
#define userSize 3

array getUsers(AttributeGroup adminGroup, AttributeGroup userGroup, Attribute queryGroupsAttribute)
{
    array users;

    // Initialize users with sample data
    users[0] = User("1", "Alice", Address("123 Main St", "", "Anytown", "CA", "12345", true));
    users[0].addAttributeGroup(adminGroup);
    users[0].addAttributeGroup(userGroup);

    users[1] = User("2", "Bob", Address("456 Elm St", "", "Othertown", "NY", "67890"));
    users[1].addAttributeGroup(userGroup);

    users[2] = User("3", "Charlie", Address("789 Oak St", "", "Somewhere", "CA", "54321", false));
    users[2].addAttribute(queryGroupsAttribute);

    return users;
}

int main()
{

    Attribute queryGroupsAttribute = Attribute("1", "QUERY_GROUPS");

    Attribute mutateCreateGroupAttribute = Attribute("2", "MUTATE_ADD_GROUP_ATTRIBUTE");

    Attribute queryUsersAttribute = Attribute("3", "QUERY_USERS");

    AttributeGroup adminGroup = AttributeGroup("1", "Administrator");

    adminGroup.addAttribute(queryGroupsAttribute);
    adminGroup.addAttribute(mutateCreateGroupAttribute);

    AttributeGroup userGroup = AttributeGroup("2", "Basic User");

    userGroup.addAttribute(queryUsersAttribute);

    array users = getUsers(adminGroup, userGroup, queryGroupsAttribute);

    string runningUserId;
    cout << "Which user will be running the user access request? (1, 2, or 3): ";
    cin >> runningUserId;

    User runningUser;
    for (User u : users)
    {
        if (u.getId() == runningUserId)
        {
            runningUser = u;
            break;
        };
    };

    string userId;
    cout << "Which user would you like to view? (1, 2, or 3): ";
    cin >> userId;

    if (!runningUser.hasAttribute("QUERY_USERS"))
    {
        cout << "User does not have access to query users." << endl;
        return 0;
    }

    User user;
    for (User u : users)
    {
        if (u.getId() == userId)
        {
            user = u;
            break;
        };
    };

    cout << "User ID: " << user.getId() << endl;
    cout << "Name: " << user.getName() << endl;
    cout << "Address: " << user.getAddress().getAddress() << endl;

    if (!runningUser.hasAttribute("QUERY_GROUPS"))
    {
        return 0;
    }

    cout << "User has access to query groups." << endl;

    for (AttributeGroup group : user.getAttributeGroups())
    {
        cout << group.getName() << endl;
    }

    return 0;
}

I assume the comments are pretty clear what's going on. But to be clear...

  • Attributes hold the list of user attributes (permissions) that a user has to determine what they can do.
  • Attributes are linked to a user, and access checks are never done directly done on a group, instead always an attribute.
  • To make lives easier for an administrator or stakeholder to assign attributes to a user, attributes can be grouped together in an AttributeGroup (often called a role).

In this example, when an attribute group is assigned to a user we roughly loop over each of the attributes and add them to the user. In the real world the attributed on the group might change over time, so the user attributes would also need to reflect these changes. So a better `hasAttribute()` function would loop over the attributes in the group and the attributes on the user. We wouldn't normally just add every attribute to the user since that's just a snapshot in time.

Full code

Comments

Popular posts from this blog

Red-Gate SQL Compare

Every now and then I come across a program that becomes so ingrained in my daily work that I hardly know how I'd get by without it.  I'll probably break down a couple over the next few days, but for database work, I have never found anything as good as Red Gate's SQL Compare and SQL Data Compare .  Essentially these tools let you compare two SQL Server databases (all objects, users, permissions, functions, diagrams, anything) and update changes to whichever database you want.  This is amazingly useful for deploying database changes to a test or production environment (do it to production with ridiculous care, even though it will generate a SQL Script for you and run all updates in one transaction), and making sure everything is synchronized. For releases we can just generate the compare script, confirm that the changes match the updates we want to go out, and store it all in one place with the release details.  This is true for both the structure and the d...

Advantages and Disadvantages of Using Microsoft Access

I've answered this question in some form or another far more times than I care to count.  Most often it's a question of "why do I need a fancy Web application when I can just build this myself in two days in Access.  I mean, the data's already in Excel."  So I figured I'd post out what I threw together, I know I've missed some points. Overview Microsoft Access is an ideal solution for relatively small datasets and a limited number of users. From the Microsoft Web site: “As a desktop database, Access is well suited for small, departmental applications. These applications may start as one user’s project. For example, an employee realizes that productivity can be increased if a paper-based process is automated with an Access application. Other users in the department recognize that they can take advantage of the application if additional features are added. As more features are added, more employees run the application. As time goes by, more and more Access...

Portrait Innovations

I mentioned in a previous post that I surprised Erin with pictures from Portrait Innovations of Rachel, Colin and (a few) of myself.  This generated more comments than 99% of my posts receive (read that as I received one comment).  Because both Erin and I have gone separately and had great experiences with the company, I figured I would spend a post explaining how it works, what's different from Sears and a professional photographer, and why I like them. Great Shots Erin and I went to Sears a LOT when Rachel was born.  We went every month to track her growth.  So far Colin's on an every 3 months cycle (second kid, life sucks, get over it and quit whining to me about being "fair").  One of the things with Sears that kind of bugged me was the set positions.  You know, laying on the floor with head propped on arms, sitting on stool cross legged.  While I think having some set poses are a good idea, at both Sears we've used, it's too extreme....