> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/appertafoundation/openeyes/llms.txt
> Use this file to discover all available pages before exploring further.

# User Management & Permissions

> Managing users, roles, and access control in OpenEyes

OpenEyes implements a comprehensive role-based access control (RBAC) system for managing user permissions across the application.

## User Management

### Accessing User Administration

The admin interface is accessible at `/admin/users` and is managed by the `AdminController`:

<Steps>
  <Step title="Navigate to Admin Section">
    Access via main menu → Admin → Users
  </Step>

  <Step title="Search for Users">
    Use the search box to filter by name, ID, or username
  </Step>

  <Step title="Add or Edit Users">
    Click "Add User" or select an existing user to modify
  </Step>
</Steps>

### User Model Structure

The User model (`protected/models/User.php`) contains:

<ParamField path="id" type="integer">
  Unique user identifier
</ParamField>

<ParamField path="first_name" type="string" required>
  User's first name (max 40 characters)
</ParamField>

<ParamField path="last_name" type="string" required>
  User's last name (max 40 characters)
</ParamField>

<ParamField path="email" type="string" required>
  User's email address (max 80 characters)
</ParamField>

<ParamField path="title" type="string">
  Professional title (Dr, Mr, Mrs, Ms, etc.)
</ParamField>

<ParamField path="global_firm_rights" type="boolean" default="1">
  Whether user has access to all firms/contexts
</ParamField>

<ParamField path="is_consultant" type="boolean">
  Mark user as a consultant
</ParamField>

<ParamField path="is_surgeon" type="boolean">
  Mark user as a surgeon (requires additional fields)
</ParamField>

<ParamField path="doctor_grade_id" type="integer">
  Doctor grade (required if `is_surgeon = 1`)
</ParamField>

<ParamField path="registration_code" type="string">
  Professional registration code (required for surgeons)
</ParamField>

<ParamField path="contact_id" type="integer">
  Link to contact record with full details
</ParamField>

### Creating a New User

When creating a user, the system requires:

1. **Basic Information**: Name, title, email
2. **Authentication**: At least one institution authentication
3. **Roles**: One or more system roles
4. **Context Access**: Firm assignments (if `global_firm_rights = 0`)

<CodeGroup>
  ```php User Creation Flow theme={null}
  // protected/controllers/AdminController.php:947
  public function actionEditUser($id = null)
  {
      $user = $id ? User::model()->findByPk($id) : new User();
      
      if ($request->getIsPostRequest()) {
          // Validate and save user
          $user->attributes = $request->getPost('User');
          
          if ($user->save()) {
              // Save contact
              $contact->save();
              
              // Save roles
              $user->saveRoles($user_attributes['roles']);
              
              // Save firm assignments
              $user->saveFirms($user_attributes['firms']);
              
              // Save authentication entries
              foreach ($user_auths_attributes as $auth) {
                  $user_auth->save();
              }
          }
      }
  }
  ```

  ```php Validation Rules theme={null}
  // protected/models/User.php:79
  public function rules()
  {
      return [
          ['email, first_name, last_name, global_firm_rights', 'required'],
          ['first_name, last_name', 'length', 'max' => 40],
          ['email', 'email'],
          // Surgeon-specific rules
          ['doctor_grade_id, registration_code', 'required', 
           'on' => 'is_surgeon'],
      ];
  }
  ```
</CodeGroup>

<Warning>
  When `global_firm_rights` is set to 0 (No), at least one firm must be assigned to the user or validation will fail.
</Warning>

## User Authentication

OpenEyes supports multiple authentication methods per user through the `UserAuthentication` model.

### Authentication Types

<Tabs>
  <Tab title="BASIC (Local)">
    Local database authentication with password management.

    **Features:**

    * Password complexity requirements
    * Password expiration
    * Account lockout after failed attempts
    * Password history tracking

    Configured via `AUTH_SOURCE=BASIC` environment variable.
  </Tab>

  <Tab title="LDAP">
    External LDAP/Active Directory authentication.

    **Configuration:**

    ```bash theme={null}
    AUTH_SOURCE=LDAP
    OE_LDAP_SERVER=ldap://ldap.example.com:389
    OE_LDAP_BIND_DN=cn=readonly,dc=example,dc=com
    OE_LDAP_BASE_DN=ou=users,dc=example,dc=com
    ```

    No password stored locally for LDAP users.
  </Tab>

  <Tab title="SAML">
    SAML-based single sign-on.

    **Configuration:**

    ```bash theme={null}
    AUTH_SOURCE=SAML
    SSO_BASE_URL=https://openeyes.example.com
    SSO_ENTITY_ID=https://idp.example.com
    SSO_APP_EMBED_LINK=https://app.example.com/embed
    ```
  </Tab>

  <Tab title="OIDC">
    OpenID Connect authentication.

    **Configuration:**

    ```bash theme={null}
    AUTH_SOURCE=OIDC
    SSO_PROVIDER_URL=https://auth.example.com
    SSO_CLIENT_ID=openeyes-client
    SSO_CLIENT_SECRET=secret_value
    SSO_ISSUER_URL=https://auth.example.com
    SSO_REDIRECT_URL=https://openeyes.example.com/oidc/callback
    ```
  </Tab>
</Tabs>

### UserAuthentication Model

**File:** `protected/models/UserAuthentication.php`

<ParamField path="id" type="integer">
  Authentication record ID
</ParamField>

<ParamField path="user_id" type="integer" required>
  Reference to user
</ParamField>

<ParamField path="institution_authentication_id" type="integer" required>
  Links to institution's authentication method
</ParamField>

<ParamField path="username" type="string" required>
  Login username (max 40 characters)
</ParamField>

<ParamField path="password_hash" type="string">
  Hashed password (for BASIC auth only)
</ParamField>

<ParamField path="password_salt" type="string">
  Password salt (legacy, being phased out)
</ParamField>

<ParamField path="password_status" type="string">
  Status: `current`, `expired`, `stale`, or `softlocked`
</ParamField>

<ParamField path="password_failed_tries" type="integer">
  Failed login attempt counter
</ParamField>

<ParamField path="password_last_changed_date" type="datetime">
  Last password change timestamp
</ParamField>

<ParamField path="active" type="boolean" default="true">
  Whether authentication is active
</ParamField>

### Password Management

```php theme={null}
// protected/models/UserAuthentication.php:244
public function verifyPassword($password)
{
    // Modern password_verify method
    if (!$this->password_salt) {
        return password_verify($password, $this->password_hash);
    }
    
    // Legacy method (auto-upgrades to modern)
    if (PasswordUtils::hashPassword($password, $this->password_salt) 
        === $this->password_hash) {
        // Re-hash with modern method
        $this->password_salt = null;
        $this->password_hash = PasswordUtils::hashPassword($password, null);
        $this->save();
        return true;
    }
    
    return false;
}
```

<Info>
  OpenEyes automatically upgrades legacy password hashes to the modern `password_hash()` method on successful login.
</Info>

## Role-Based Access Control (RBAC)

OpenEyes uses Yii's built-in RBAC system with three database tables:

* `authitem` - Roles, tasks, and operations
* `authitemchild` - Role hierarchy
* `authassignment` - User role assignments

### Default Roles

<CardGroup cols={2}>
  <Card title="admin" icon="user-shield">
    **System Administrator**

    Full access to all system features including:

    * User management
    * System settings
    * All institutions
    * Module administration
  </Card>

  <Card title="User" icon="user">
    **Standard User**

    Basic clinical access:

    * Patient records
    * Event creation
    * Clinical notes
    * Limited to assigned contexts
  </Card>

  <Card title="Prescribe" icon="prescription">
    **Prescriber**

    Permission to prescribe medications:

    * Create prescriptions
    * Manage drug lists
    * View medication history
  </Card>

  <Card title="Med Administer" icon="pills">
    **Medication Administrator**

    Permission to administer medications:

    * Record administration
    * Document adverse reactions
  </Card>

  <Card title="Edit" icon="pen">
    **Editor**

    Enhanced editing permissions:

    * Edit locked events
    * Modify historical data
  </Card>

  <Card title="View clinical" icon="eye">
    **Clinical Viewer**

    View-only clinical access:

    * Read patient records
    * View clinical events
    * No editing capabilities
  </Card>
</CardGroup>

### Checking User Permissions

```php theme={null}
// Check if user has specific role
if (Yii::app()->authManager->checkAccess('admin', $userId)) {
    // User is an admin
}

// Check in controller action
if (!$this->checkAccess('admin')) {
    throw new CHttpException(403, 'Access denied');
}

// Get user's roles
$roles = Yii::app()->authManager->getRoles($userId);

// Check if user has specific role (User model)
if ($user->hasRole('Prescribe')) {
    // User can prescribe
}
```

### Managing User Roles

```php theme={null}
// protected/models/User.php:549
public function saveRoles(array $roles)
{
    $old_roles = array_map(fn($role) => $role->name, $this->roles);
    $added_roles = array_diff($roles, $old_roles);
    $removed_roles = array_diff($old_roles, $roles);
    
    // Assign new roles
    foreach ($added_roles as $role) {
        Yii::app()->authManager->assign($role, $this->id);
    }
    
    // Revoke removed roles
    foreach ($removed_roles as $role) {
        Yii::app()->authManager->revoke($role, $this->id);
    }
}
```

## Firm/Context Management

Users can be restricted to specific firms (clinical contexts) when `global_firm_rights = 0`.

### Firm Assignments

A "firm" in OpenEyes represents a clinical service or team, typically associated with:

* A subspecialty (e.g., Cataract, Glaucoma, Retina)
* A consultant or service lead
* One or more sites

```php theme={null}
// Get user's available firms
$firms = $user->getAvailableFirms();

// Get firms for current institution only
$institutionFirms = $user->getFirmsForCurrentInstitution();

// Save firm assignments
$user->saveFirms([1, 5, 12]); // Array of firm IDs
```

<Warning>
  Users without `global_firm_rights` must have at least one firm assigned, or they cannot access clinical functionality.
</Warning>

## PIN Code Management

OpenEyes supports PIN-based authentication for quick actions and signing:

```php theme={null}
// Generate PIN for user
$user->generatePin();

// Regenerate existing PIN
$user->generatePin($regenerate = true);

// Verify PIN
if ($user->checkPin($pincode, $user_id, $institution_id, $site_id)) {
    // PIN verified
}

// Check if regeneration limit reached
if ($user->isPincodeRegenReachLimit()) {
    // User has reached 5 regenerations in 12 months
}
```

<Info>
  PINs can be regenerated up to 5 times within a 12-month period for security purposes (see `User.php:48`).
</Info>

## Institution-Specific Users

Non-admin users are typically restricted to their assigned institution:

```php theme={null}
// protected/controllers/AdminController.php:881
if (!$this->checkAccess('admin')) {
    // Get only users for current institution
    $institution = Yii::app()->session['selected_institution_id'];
    
    // Exclude installation admins
    $user_ids = array_diff($institution_user_ids, $admin_user_ids);
    
    $criteria->addInCondition('t.id', $user_ids);
}
```

## API Reference

### User Methods

**File:** `protected/models/User.php`

* `getFullName()` - Returns "FirstName LastName"
* `getFullNameAndTitle()` - Returns "Title FirstName LastName"
* `getRoles()` - Returns array of CAuthItem roles
* `hasRole($targetRole)` - Check if user has specific role
* `saveRoles($roles)` - Assign roles to user
* `saveFirms($firms)` - Assign firms to user
* `getAvailableFirms()` - Get firms user can access
* `generatePin($regenerate)` - Generate or regenerate PIN

### UserAuthentication Methods

**File:** `protected/models/UserAuthentication.php`

* `verifyPassword($password)` - Verify password hash
* `handlePassword()` - Process password on save
* `setPasswordHash()` - Hash password before save
* `isLocalAuth()` - Check if using local authentication

## Security Best Practices

<CardGroup cols={2}>
  <Card title="Strong Passwords" icon="lock">
    Configure password complexity requirements via `pw_restrictions` parameters
  </Card>

  <Card title="Least Privilege" icon="shield">
    Grant users only the roles they need for their work
  </Card>

  <Card title="Regular Audits" icon="clipboard-check">
    Review user accounts and permissions regularly
  </Card>

  <Card title="Disable Inactive Users" icon="user-slash">
    Set `active = 0` on UserAuthentication for inactive accounts
  </Card>
</CardGroup>

## Related Documentation

* [System Configuration](/admin/configuration)
* [Institution Management](/admin/institutions)
* [System Settings](/admin/system-settings)
