Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 15 additions & 10 deletions test-apps/02-flask-blog-api/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"""
from flask import Flask, jsonify, request, g
from flask_cors import CORS
from datetime import datetime
from datetime import datetime, timezone

# Local imports
from config import get_config
Expand All @@ -16,6 +16,13 @@
# RBAC imports
from rbac import RBAC

# Constants for error messages
ERROR_NOT_FOUND = 'Not found'
ERROR_VALIDATION = 'Validation error'
MSG_POST_NOT_FOUND = 'Post not found'
MSG_AUTH_REQUIRED = 'Authentication required'
MSG_LOGIN_REQUIRED = 'You must be logged in to perform this action'


def create_app(config_name='development'):
"""Application factory."""
Expand Down Expand Up @@ -60,7 +67,7 @@ def before_request():
@app.errorhandler(404)
def not_found(error):
return jsonify({
'error': 'Not found',
'error': ERROR_NOT_FOUND,
'message': 'The requested resource was not found'
}), 404

Expand Down Expand Up @@ -196,7 +203,7 @@ def get_current_user():

if not user_data:
return jsonify({
'error': 'Not found',
'error': ERROR_NOT_FOUND,
'message': 'User not found'
}), 404

Expand Down Expand Up @@ -315,7 +322,7 @@ def create_post():
def update_post(post_id):
"""Update a post (must be owner or editor/admin)."""
data = request.get_json()
post = g.resource # Set by decorator
# Resource validated by decorator

title = data.get('title')
content = data.get('content')
Expand Down Expand Up @@ -499,15 +506,13 @@ def update_user_role(user_id):
# Get current roles
user_details = rbac.get_user(rbac_user_id)
if user_details:
# Remove old role assignment
old_role_id = f"role_{user.role}"
# Note: The RBAC library should have revoke_role, but we'll handle it simply
# Note: The RBAC library should have revoke_role
# by reassigning - the library should handle this internally
pass

# Assign new role
rbac.assign_role(rbac_user_id, f"role_{new_role}")
except Exception as e:
except Exception:
# If user doesn't exist in RBAC, create them
try:
rbac.create_user(
Expand All @@ -516,7 +521,7 @@ def update_user_role(user_id):
name=user.username
)
rbac.assign_role(rbac_user_id, f"role_{new_role}")
except:
except Exception:
pass # User might already exist

return jsonify({
Expand Down Expand Up @@ -615,7 +620,7 @@ def setup_rbac(rbac: RBAC):

if __name__ == '__main__':
app = create_app('development')
print(f"""
print("""
╔════════════════════════════════════════════════╗
║ Flask Blog API - RBAC Test Application ║
╠════════════════════════════════════════════════╣
Expand Down
10 changes: 5 additions & 5 deletions test-apps/02-flask-blog-api/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"""
import jwt
import bcrypt
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone
from functools import wraps
from flask import request, jsonify, g
from config import Config
Expand Down Expand Up @@ -35,8 +35,8 @@ def generate_token(self, user_id: str, username: str, role: str) -> str:
'user_id': user_id,
'username': username,
'role': role,
'iat': datetime.utcnow(),
'exp': datetime.utcnow() + self.expiration
'iat': datetime.now(timezone.utc),
'exp': datetime.now(timezone.utc) + self.expiration
}
token = jwt.encode(payload, self.secret_key, algorithm=self.algorithm)
return token
Expand All @@ -55,12 +55,12 @@ def decode_token(self, token: str) -> dict:
payload = jwt.decode(token, self.secret_key, algorithms=[self.algorithm])
return payload

def extract_token_from_header(self) -> str:
def extract_token_from_header(self) -> str | None:
"""
Extract JWT token from Authorization header.

Returns:
str: Token string or None if not found
str | None: Token string or None if not found
"""
auth_header = request.headers.get('Authorization', '')
if auth_header.startswith('Bearer '):
Expand Down
3 changes: 0 additions & 3 deletions test-apps/02-flask-blog-api/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,6 @@ def decorated_function(*args, **kwargs):
rbac = g.rbac
storage = g.storage

# Build permission string
permission = f"{action}:{resource_type}" if resource_type else action

# Get resource for ownership check
resource = None
if check_ownership:
Expand Down
10 changes: 5 additions & 5 deletions test-apps/02-flask-blog-api/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Simple dataclass-based models for posts, comments, and users.
"""
from dataclasses import dataclass, field
from datetime import datetime
from datetime import datetime, timezone
from typing import Optional, List
from enum import Enum

Expand Down Expand Up @@ -56,8 +56,8 @@ class Post:
author_id: str
author_username: str
status: PostStatus = PostStatus.DRAFT
created_at: datetime = field(default_factory=datetime.utcnow)
updated_at: datetime = field(default_factory=datetime.utcnow)
created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
updated_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
published_at: Optional[datetime] = None
tags: List[str] = field(default_factory=list)
view_count: int = 0
Expand Down Expand Up @@ -110,8 +110,8 @@ class Comment:
content: str
author_id: str
author_username: str
created_at: datetime = field(default_factory=datetime.utcnow)
updated_at: datetime = field(default_factory=datetime.utcnow)
created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
updated_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
is_deleted: bool = False

def to_dict(self, include_author: bool = True) -> dict:
Expand Down
6 changes: 3 additions & 3 deletions test-apps/02-flask-blog-api/seed_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Seed data for Flask Blog API.
Loads sample users, posts, and comments for testing.
"""
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone
from models import PostStatus


Expand Down Expand Up @@ -227,7 +227,7 @@ def hello():
# Adjust created_at for variety
if post.status == PostStatus.PUBLISHED:
days_ago = len(created_posts)
post.created_at = datetime.utcnow() - timedelta(days=days_ago)
post.created_at = datetime.now(timezone.utc) - timedelta(days=days_ago)
post.published_at = post.created_at

print(f" ✓ Created post: '{post.title}' by {author.username}")
Expand Down Expand Up @@ -286,7 +286,7 @@ def hello():
if comment:
print(f" ✓ Created comment by {author.username} on '{post.title}'")

print(f"\nSeed data loaded successfully!")
print("\nSeed data loaded successfully!")
print(f" Users: {len(created_users)}")
print(f" Posts: {len(created_posts)} ({len([p for p in created_posts if p.status == PostStatus.PUBLISHED])} published)")
print(f" Comments: {len(comments_data)}")
Expand Down
2 changes: 1 addition & 1 deletion test-apps/02-flask-blog-api/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Provides simple CRUD operations for users, posts, and comments.
"""
from typing import List, Optional, Dict
from datetime import datetime
from datetime import datetime, timezone
from models import User, Post, Comment, PostStatus, SystemStats


Expand Down
1 change: 0 additions & 1 deletion test-apps/02-flask-blog-api/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,6 @@ def test_list_posts_authenticated(client, tokens):
"""Test listing posts with authentication."""
response = client.get('/posts', headers=get_auth_header(tokens['author']))
assert response.status_code == 200
data = response.json
# Authenticated users can see their own drafts + published posts


Expand Down
Loading