Core Entities
Overview
This page describes the core domain entities that form the foundation of the multi-dimensional security system.
Marker Interfaces
The security system uses marker interfaces to identify entity security classifications.
OrganisationScoped
Entities belonging to an organization implement this interface.
/**
* Marker interface for org-scoped entities
*/
public interface OrganisationScoped {
Long getOrgId();
void setOrgId(Long orgId);
}
PersonScoped
Entities containing person-specific data implement this interface.
/**
* Marker interface for person-scoped entities
*/
public interface PersonScoped {
Long getPersonId();
void setPersonId(Long personId);
}
Composite-Scoped Entities
Entities requiring both organizational and personal authorization implement both OrganisationScoped and PersonScoped interfaces directly. This compositional approach is more flexible and scalable than creating a combined interface. Each dimension is checked independently.
/**
* Example: Composite-scoped entity implementing both interfaces
*/
@Entity
public class EventEntry implements OrganisationScoped, PersonScoped {
// Implements both getOrgId/setOrgId and getPersonId/setPersonId
// Each security dimension is checked separately - both must pass
}
This approach allows each security dimension to be evaluated independently and makes it easier to add additional security facets in the future without creating multiple interface combinations.
Enumerations
AccessLevel
Defines the level of access a user has.
package com.example.security.domain;
/ * Access level enumeration for multi-dimensional security. * Defines the level of access a user has to organizations or persons. */ public enum AccessLevel { / * Read-only access. Can view/query data but cannot create, update, or delete. */ READ, / * Full access. Can view, create, update, and delete data. */ READ_WRITE; / * Check if this access level satisfies a required access level. * * @param required The required access level * @return true if this access level is sufficient */ public boolean satisfies(AccessLevel required) { if (required == READ) { // Both READ and READ_WRITE satisfy READ requirement return true; } // Only READ_WRITE satisfies READ_WRITE requirement return this == READ_WRITE; } }
Key features:
-
Two levels: READ and READ_WRITE
-
satisfies()method implements hierarchy logic -
READ_WRITE satisfies READ requirements
LinkType
Categorizes the type of person-to-person relationship.
package com.example.security.domain;
/ * Type of person-to-person relationship link. * Categorizes the nature of delegated access between principals. */ public enum LinkType { / * Family relationship (parent-child, spouse, etc.). * Typically grants READ_WRITE access. */ FAMILY, / * Team manager to team members relationship. * Typically grants READ access to view team roster. */ TEAM_MANAGER, / * Coach to athletes relationship. * Typically grants READ_WRITE access to manage athlete data. */ COACH, / * Legal guardian relationship. * Typically grants READ_WRITE access. */ GUARDIAN, / * General delegation for temporary or special access. * Access level varies based on delegation purpose. */ DELEGATE }
Common use cases:
-
FAMILY: Parent-child relationships
-
TEAM_MANAGER: Team manager to team members
-
COACH: Coach to athletes
-
GUARDIAN: Legal guardian relationships
-
DELEGATE: General delegation
SecurityType
Classifies entities by their security requirements.
package com.example.security.domain;
/ * Security classification for entities. * Determines which security dimensions apply and how authorization is performed. */ public enum SecurityType { / * Entity has direct orgId field. * Security checked via organizational dimension only. * Example: Event, Organization */ ORG_SCOPED, / * Entity has direct personId field. * Security checked via personal dimension only. * Example: PersonProfile, MedicalInfo */ PERSON_SCOPED, / * Entity has both orgId and personId fields (direct or transitive). * Security must pass BOTH dimensional checks (AND logic). * Example: EventEntry, Payment */ DUAL_SCOPED, / * Entity inherits orgId through parent relationship. * Security checked via join to parent’s orgId. * Example: Race (via Event) */ TRANSITIVE_ORG, / * Entity inherits personId through parent relationship. * Security checked via join to parent’s personId. * Example: PersonAddress (via PersonProfile) */ TRANSITIVE_PERSON, / * Entity has direct personId but transitive orgId. * Common for dual-scoped entities where org comes from a parent. * Example: EventEntry (personId direct, orgId via Event) */ DUAL_TRANSITIVE_ORG, / * No security filtering applied. * Rare - typically for admin-only or public reference data. */ NONE }
Used by QueryService implementations to determine which security specifications to apply.
User and Authentication
OrgUser
The authenticated user account entity.
@Entity
public class OrgUser {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false, length = 50)
private String login;
@Column(nullable = false)
private String passwordHash;
@Column(nullable = false)
private boolean activated = true;
@ManyToOne(optional = false)
@JoinColumn(name = "primary_org_id")
private Organisation primaryOrganisation;
@OneToMany(mappedBy = "orgUser", fetch = FetchType.EAGER)
private Set<LinkedOrg> linkedOrganisations = new HashSet<>();
@ManyToOne(optional = false)
@JoinColumn(name = "principal_person_id")
private Principal principalPerson;
@OneToMany(mappedBy = "fromPrincipal", fetch = FetchType.EAGER)
private Set<LinkedPerson> linkedPersons = new HashSet<>();
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(
name = "org_user_authority",
joinColumns = {@JoinColumn(name = "user_id")},
inverseJoinColumns = {@JoinColumn(name = "authority_name")}
)
private Set<Authority> authorities = new HashSet<>();
// Standard getters and setters
}
Key relationships:
-
One primary organization (mandatory, READ_WRITE access)
-
Multiple linked organizations (optional, with explicit access levels)
-
One principal person (mandatory, the user’s identity)
-
Multiple linked persons (optional, delegated access)
-
Multiple authorities/roles
Authority
Role/authority entity for Spring Security.
@Entity
public class Authority implements Serializable {
@Id
@Column(length = 50)
private String name;
// getters and setters
}
Common authorities:
-
ROLE_ADMIN: System administrator -
ROLE_USER: Regular user -
ROLE_GLOBAL_VIEWER: Can read all organizations -
ROLE_AUDITOR: Can audit all data
Organization Dimension
Organisation
The organization entity.
@Entity
public class Organisation {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 200)
private String name;
@Column(length = 50)
private String shortName;
@Column(nullable = false)
private boolean active = true;
@Enumerated(EnumType.STRING)
@Column(length = 50)
private OrganisationType type;
// Contact information
private String email;
private String phone;
private String website;
// Address fields
private String addressLine1;
private String addressLine2;
private String city;
private String postcode;
private String country;
@Column(columnDefinition = "TEXT")
private String description;
// Audit fields
@CreatedDate
private Instant createdDate;
@LastModifiedDate
private Instant lastModifiedDate;
// getters and setters
}
LinkedOrg
Links a user to a secondary organization with specific access level.
@Entity
@Table(
name = "linked_org",
uniqueConstraints = @UniqueConstraint(
columnNames = {"org_user_id", "organisation_id"}
)
)
public class LinkedOrg {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(optional = false)
@JoinColumn(name = "org_user_id")
private OrgUser orgUser;
@ManyToOne(optional = false)
@JoinColumn(name = "organisation_id")
private Organisation organisation;
@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 20)
private AccessLevel accessLevel;
@Column(nullable = false)
private boolean active = true;
// Optional time-bound access
private Instant validFrom;
private Instant validTo;
// Audit fields
@CreatedDate
private Instant createdDate;
@CreatedBy
private String createdBy;
@LastModifiedDate
private Instant lastModifiedDate;
@LastModifiedBy
private String lastModifiedBy;
// Business logic
public boolean isCurrentlyValid() {
Instant now = Instant.now();
if (validFrom != null && now.isBefore(validFrom)) {
return false;
}
if (validTo != null && now.isAfter(validTo)) {
return false;
}
return active;
}
// getters and setters
}
Features:
-
Unique constraint: One user-org pair per entry
-
Active flag for soft delete
-
Time-bound access with validFrom/validTo
-
Full audit trail
Person Dimension
Principal
The principal person entity representing the authenticated user’s identity.
@Entity
public class Principal {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 100)
private String firstName;
@Column(nullable = false, length = 100)
private String lastName;
@Column(length = 100)
private String middleName;
@Column(nullable = false)
private LocalDate dateOfBirth;
@Enumerated(EnumType.STRING)
@Column(length = 20)
private Gender gender;
@Column(length = 100)
private String nationality;
// Contact
@Column(length = 100)
private String email;
@Column(length = 20)
private String phone;
@OneToOne(mappedBy = "principalPerson")
private OrgUser orgUser;
// Audit fields
@CreatedDate
private Instant createdDate;
@LastModifiedDate
private Instant lastModifiedDate;
// Computed fields
public String getFullName() {
return firstName + " " + lastName;
}
public Integer getAge() {
return Period.between(dateOfBirth, LocalDate.now()).getYears();
}
// getters and setters
}
LinkedPerson
Links a principal to another person with delegated access.
@Entity
@Table(
name = "linked_person",
uniqueConstraints = @UniqueConstraint(
columnNames = {"from_principal_id", "to_person_id"}
)
)
public class LinkedPerson {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(optional = false)
@JoinColumn(name = "from_principal_id")
private Principal fromPrincipal;
@Column(name = "to_person_id", nullable = false)
private Long toPersonId;
@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 20)
private AccessLevel accessLevel;
@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 30)
private LinkType linkType;
@Column(nullable = false)
private boolean active = true;
// Optional time-bound access
private Instant validFrom;
private Instant validTo;
// Optional notes about the relationship
@Column(columnDefinition = "TEXT")
private String notes;
// Audit fields
@CreatedDate
private Instant createdDate;
@CreatedBy
private String createdBy;
@LastModifiedDate
private Instant lastModifiedDate;
@LastModifiedBy
private String lastModifiedBy;
// Business logic
public boolean isCurrentlyValid() {
Instant now = Instant.now();
if (validFrom != null && now.isBefore(validFrom)) {
return false;
}
if (validTo != null && now.isAfter(validTo)) {
return false;
}
return active;
}
// getters and setters
}
Features:
-
Links from one principal to another person (by ID)
-
Supports different relationship types (FAMILY, TEAM_MANAGER, etc.)
-
Access level per relationship
-
Time-bound and activatable
-
Full audit trail
Database Schema
ERD Overview
┌──────────────┐
│ OrgUser │
├──────────────┤
│ id │◄──┐
│ login │ │
│ passwordHash │ │
└──────────────┘ │
│ │
│ 1 │
│ │
│ * │
┌──────────────┐ │
│ LinkedOrg │ │
├──────────────┤ │
│ id │ │
│ org_user_id │───┘
│ org_id │───┐
│ accessLevel │ │
│ active │ │
└──────────────┘ │
│ *
│
┌──▼──────────┐
│Organisation │
├─────────────┤
│ id │
│ name │
│ active │
└─────────────┘
┌──────────────┐
│ OrgUser │
├──────────────┤
│ principal_id │───┐
└──────────────┘ │
│ 1
│
┌──▼──────────┐
│ Principal │
├─────────────┤
│ id │◄──┐
│ firstName │ │
│ lastName │ │
└─────────────┘ │
│ │
│ 1 │
│ │
│ * │
┌──────▼──────┐ │
│LinkedPerson │ │
├─────────────┤ │
│ id │ │
│ from_pr_id │───┘
│ to_person_id│
│ accessLevel │
│ linkType │
│ active │
└─────────────┘
Key Tables
| Table | Purpose | Key Fields |
|---|---|---|
org_user |
User accounts |
login, primary_org_id, principal_person_id |
linked_org |
Secondary org access |
org_user_id, organisation_id, access_level |
organisation |
Organizations |
name, active |
principal |
Person identities |
firstName, lastName, dateOfBirth |
linked_person |
Delegated person access |
from_principal_id, to_person_id, access_level, link_type |
Next Steps
-
See Security Entities for supporting entities
-
Review Entity Examples for concrete implementations
-
Explore Security Service Implementation