0
# Data Models
1
2
SQLAlchemy models representing the security schema including users, roles, permissions, actions, resources, and their relationships. These models provide the data layer foundation for all security operations.
3
4
## Capabilities
5
6
### User Model
7
8
Represents an Airflow user with authentication information, profile data, and role assignments.
9
10
```python { .api }
11
class User(Model):
12
"""
13
Represents an Airflow user which has roles assigned to it.
14
15
Attributes:
16
- id: Integer primary key
17
- first_name: String(64), user's first name
18
- last_name: String(64), user's last name
19
- username: String(256), unique username
20
- password: String(256), hashed password
21
- active: Boolean, whether user account is active
22
- email: String(256), unique email address
23
- last_login: DateTime, timestamp of last login
24
- login_count: Integer, total number of logins
25
- fail_login_count: Integer, number of failed login attempts
26
- roles: Relationship to Role objects
27
- created_on: DateTime, account creation timestamp
28
- changed_on: DateTime, last modification timestamp
29
- created_by_fk: Foreign key to User who created this account
30
- changed_by_fk: Foreign key to User who last modified this account
31
- created_by: Relationship to User (creator)
32
- changed_by: Relationship to User (modifier)
33
"""
34
35
@classmethod
36
def get_user_id(cls) -> int | None:
37
"""Get current user ID from Flask context."""
38
39
@property
40
def is_authenticated(self) -> bool:
41
"""Check if user is authenticated (always True for User objects)."""
42
43
@property
44
def is_active(self) -> bool:
45
"""Check if user account is active."""
46
47
@property
48
def is_anonymous(self) -> bool:
49
"""Check if user is anonymous (always False for User objects)."""
50
51
@property
52
def perms(self) -> set[tuple[str, str]]:
53
"""Get user's permissions as set of (action, resource) tuples."""
54
55
def get_id(self) -> int:
56
"""Get user ID for session management."""
57
58
def get_full_name(self) -> str:
59
"""Get formatted full name (first_name last_name)."""
60
```
61
62
### Role Model
63
64
Represents a user role that can be assigned permissions and associated with users.
65
66
```python { .api }
67
class Role(Model):
68
"""
69
Represents a user role to which permissions can be assigned.
70
71
Attributes:
72
- id: Integer primary key
73
- name: String(64), unique role name
74
- permissions: Relationship to Permission objects assigned to this role
75
"""
76
```
77
78
### Permission Model
79
80
Represents a permission pairing an action with a resource for granular access control.
81
82
```python { .api }
83
class Permission(Model):
84
"""
85
Permission pair comprised of an Action + Resource combo.
86
87
Attributes:
88
- id: Integer primary key
89
- action_id: Foreign key to Action
90
- action: Relationship to Action object
91
- resource_id: Foreign key to Resource
92
- resource: Relationship to Resource object
93
"""
94
```
95
96
### Action Model
97
98
Represents permission actions that define what operations can be performed.
99
100
```python { .api }
101
class Action(Model):
102
"""
103
Represents permission actions such as 'can_read', 'can_edit', etc.
104
105
Attributes:
106
- id: Integer primary key
107
- name: String(100), unique action name
108
"""
109
```
110
111
### Resource Model
112
113
Represents permission objects/resources that can be protected by permissions.
114
115
```python { .api }
116
class Resource(Model):
117
"""
118
Represents permission objects such as 'User', 'DAG', 'Connection', etc.
119
120
Attributes:
121
- id: Integer primary key
122
- name: String(250), unique resource name
123
"""
124
125
def __eq__(self, other) -> bool:
126
"""Check equality based on resource name."""
127
128
def __neq__(self, other) -> bool:
129
"""Check inequality based on resource name."""
130
```
131
132
### RegisterUser Model
133
134
Represents a user registration request for user self-registration workflows.
135
136
```python { .api }
137
class RegisterUser(Model):
138
"""
139
Represents a user registration request.
140
141
Attributes:
142
- id: Integer primary key
143
- first_name: String(64), requested first name
144
- last_name: String(64), requested last name
145
- username: String(256), requested unique username
146
- password: String(256), hashed password
147
- email: String(256), email address
148
- registration_date: DateTime, when registration was requested
149
- registration_hash: String(256), unique hash for registration verification
150
"""
151
```
152
153
### Association Tables
154
155
Many-to-many relationship tables connecting the core models.
156
157
```python { .api }
158
assoc_permission_role = Table(
159
"ab_permission_view_role",
160
Model.metadata,
161
Column("id", Integer, primary_key=True),
162
Column("permission_view_id", Integer, ForeignKey("ab_permission_view.id")),
163
Column("role_id", Integer, ForeignKey("ab_role.id")),
164
UniqueConstraint("permission_view_id", "role_id")
165
)
166
"""Many-to-many relationship table between Permission and Role."""
167
168
assoc_user_role = Table(
169
"ab_user_role",
170
Model.metadata,
171
Column("id", Integer, primary_key=True),
172
Column("user_id", Integer, ForeignKey("ab_user.id")),
173
Column("role_id", Integer, ForeignKey("ab_role.id")),
174
UniqueConstraint("user_id", "role_id")
175
)
176
"""Many-to-many relationship table between User and Role."""
177
```
178
179
## Database Schema
180
181
### Table Names
182
183
The models map to specific database tables:
184
- `User` → `ab_user`
185
- `Role` → `ab_role`
186
- `Permission` → `ab_permission_view`
187
- `Action` → `ab_permission`
188
- `Resource` → `ab_view_menu`
189
- `RegisterUser` → `ab_register_user`
190
191
### Relationships
192
193
- **User ↔ Role**: Many-to-many via `assoc_user_role`
194
- **Role ↔ Permission**: Many-to-many via `assoc_permission_role`
195
- **Permission → Action**: Many-to-one
196
- **Permission → Resource**: Many-to-one
197
- **User → User**: Self-referencing for created_by/changed_by
198
199
## Usage Examples
200
201
### Working with User Model
202
203
```python
204
from airflow.www.fab_security.sqla.models import User, Role
205
206
# Create user instance
207
user = User()
208
user.username = "john_doe"
209
user.email = "john@example.com"
210
user.first_name = "John"
211
user.last_name = "Doe"
212
user.active = True
213
214
# Check user properties
215
if user.is_active:
216
print(f"Active user: {user.get_full_name()}")
217
218
# Access user permissions
219
for action, resource in user.perms:
220
print(f"Permission: {action} on {resource}")
221
```
222
223
### Role and Permission Relationships
224
225
```python
226
from airflow.www.fab_security.sqla.models import Role, Permission, Action, Resource
227
228
# Create role
229
role = Role(name="DataAnalyst")
230
231
# Create permission components
232
action = Action(name="can_read")
233
resource = Resource(name="Reports")
234
permission = Permission(action=action, resource=resource)
235
236
# Assign permission to role
237
role.permissions.append(permission)
238
239
# Access role permissions
240
for perm in role.permissions:
241
print(f"Role {role.name} can {perm.action.name} on {perm.resource.name}")
242
```
243
244
### Registration Workflow
245
246
```python
247
import uuid
248
from datetime import datetime
249
from airflow.www.fab_security.sqla.models import RegisterUser
250
251
# Create registration request
252
registration = RegisterUser()
253
registration.username = "new_user"
254
registration.first_name = "New"
255
registration.last_name = "User"
256
registration.email = "new@example.com"
257
registration.registration_date = datetime.now()
258
registration.registration_hash = str(uuid.uuid1())
259
260
print(f"Registration hash: {registration.registration_hash}")
261
```
262
263
### Query Examples
264
265
```python
266
from sqlalchemy.orm import sessionmaker
267
from airflow.www.fab_security.sqla.models import User, Role
268
269
# Query users by role
270
admin_role = session.query(Role).filter_by(name="Admin").first()
271
admin_users = session.query(User).filter(User.roles.contains(admin_role)).all()
272
273
# Query permissions for user
274
user = session.query(User).filter_by(username="john_doe").first()
275
if user:
276
# Get all permissions through roles
277
all_permissions = []
278
for role in user.roles:
279
all_permissions.extend(role.permissions)
280
```
281
282
## Model Constraints
283
284
### Unique Constraints
285
286
- `User.username`: Must be unique across all users
287
- `User.email`: Must be unique across all users
288
- `Role.name`: Must be unique across all roles
289
- `Action.name`: Must be unique across all actions
290
- `Resource.name`: Must be unique across all resources
291
- `Permission(action_id, resource_id)`: Unique combination constraint
292
293
### Foreign Key Constraints
294
295
- `Permission.action_id` → `Action.id`
296
- `Permission.resource_id` → `Resource.id`
297
- `User.created_by_fk` → `User.id` (self-reference)
298
- `User.changed_by_fk` → `User.id` (self-reference)
299
300
## Metadata Integration
301
302
All models inherit from Flask-AppBuilder's `Model` class and use Airflow's base metadata for table creation and management, ensuring compatibility with Airflow's database migration system.