489 lines
14 KiB
Markdown
489 lines
14 KiB
Markdown
# Milestone 2: Permission System
|
|
|
|
**Status**: ✅ Complete
|
|
**Goal**: Gate API access based on app permissions.
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
This milestone implements the permission system that controls which APIs an app can access. It defines permission categories (Normal, Dangerous, Signature) and provides mechanisms for checking permissions at runtime.
|
|
|
|
### Key Deliverables
|
|
|
|
1. **PermissionGate class** - Permission checking and enforcement
|
|
2. **Permission categories** - Normal/Dangerous/Signature classification
|
|
3. **User gesture tracking** - Detect recent user interactions
|
|
4. **Manifest parsing** - Read app permissions from manifest.json
|
|
|
|
---
|
|
|
|
## File Structure
|
|
|
|
```
|
|
src/main/cpp/sandbox/
|
|
├── lua_sandbox.h # (existing)
|
|
├── lua_sandbox.cpp # (existing)
|
|
├── permission_gate.h # NEW - Permission checking
|
|
└── permission_gate.cpp # NEW - Implementation
|
|
|
|
sandbox-test/
|
|
├── scripts/
|
|
│ ├── test_permission_normal.lua # NEW
|
|
│ ├── test_permission_dangerous.lua # NEW
|
|
│ └── test_permission_signature.lua # NEW
|
|
└── src/
|
|
└── main.cpp # Add new tests
|
|
```
|
|
|
|
---
|
|
|
|
## Implementation Details
|
|
|
|
### 1. Permission Categories
|
|
|
|
```cpp
|
|
// permission_gate.h
|
|
enum class PermissionCategory {
|
|
Normal, // Auto-granted (e.g., vibrate, internet)
|
|
Dangerous, // Requires user consent (e.g., camera, location)
|
|
Signature // System apps only (e.g., system settings, install apps)
|
|
};
|
|
```
|
|
|
|
### 2. Permission Definitions
|
|
|
|
```cpp
|
|
// permission_gate.cpp
|
|
static const std::unordered_map<std::string, PermissionInfo> PERMISSIONS = {
|
|
// Normal permissions (auto-granted)
|
|
{"internet", {PermissionCategory::Normal, "Access the internet"}},
|
|
{"vibrate", {PermissionCategory::Normal, "Vibrate the device"}},
|
|
{"wake_lock", {PermissionCategory::Normal, "Keep device awake"}},
|
|
{"notifications", {PermissionCategory::Normal, "Show notifications"}},
|
|
|
|
// Dangerous permissions (require user consent)
|
|
{"camera", {PermissionCategory::Dangerous, "Access the camera"}},
|
|
{"microphone", {PermissionCategory::Dangerous, "Record audio"}},
|
|
{"location.fine", {PermissionCategory::Dangerous, "Access precise location"}},
|
|
{"location.coarse", {PermissionCategory::Dangerous, "Access approximate location"}},
|
|
{"contacts.read", {PermissionCategory::Dangerous, "Read contacts"}},
|
|
{"contacts.write", {PermissionCategory::Dangerous, "Modify contacts"}},
|
|
{"storage.external", {PermissionCategory::Dangerous, "Access external storage"}},
|
|
{"sensors.motion", {PermissionCategory::Dangerous, "Access motion sensors"}},
|
|
{"bluetooth", {PermissionCategory::Dangerous, "Use Bluetooth"}},
|
|
{"calendar.read", {PermissionCategory::Dangerous, "Read calendar"}},
|
|
{"calendar.write", {PermissionCategory::Dangerous, "Modify calendar"}},
|
|
|
|
// Signature permissions (system apps only)
|
|
{"system.settings", {PermissionCategory::Signature, "Modify system settings"}},
|
|
{"system.install", {PermissionCategory::Signature, "Install apps"}},
|
|
{"system.uninstall", {PermissionCategory::Signature, "Uninstall apps"}},
|
|
{"system.admin", {PermissionCategory::Signature, "Device administrator"}},
|
|
};
|
|
```
|
|
|
|
### 3. PermissionGate Class
|
|
|
|
```cpp
|
|
// permission_gate.h
|
|
#pragma once
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
#include <unordered_set>
|
|
#include <chrono>
|
|
|
|
struct lua_State;
|
|
|
|
namespace mosis {
|
|
|
|
struct SandboxContext; // Forward declaration
|
|
|
|
enum class PermissionCategory {
|
|
Normal,
|
|
Dangerous,
|
|
Signature
|
|
};
|
|
|
|
struct PermissionInfo {
|
|
PermissionCategory category;
|
|
std::string description;
|
|
};
|
|
|
|
class PermissionGate {
|
|
public:
|
|
explicit PermissionGate(const SandboxContext& context);
|
|
|
|
// Check if app has permission (throws Lua error if not)
|
|
bool Check(lua_State* L, const std::string& permission);
|
|
|
|
// Check without throwing (returns false if denied)
|
|
bool HasPermission(const std::string& permission) const;
|
|
|
|
// Get permission category
|
|
static PermissionCategory GetCategory(const std::string& permission);
|
|
|
|
// User gesture tracking
|
|
void RecordUserGesture();
|
|
bool HasRecentUserGesture(int ms = 5000) const;
|
|
|
|
// Runtime permission grant (called after user consent)
|
|
void GrantPermission(const std::string& permission);
|
|
void RevokePermission(const std::string& permission);
|
|
|
|
// Get all declared permissions
|
|
const std::vector<std::string>& GetDeclaredPermissions() const;
|
|
|
|
// Get all granted permissions
|
|
std::vector<std::string> GetGrantedPermissions() const;
|
|
|
|
private:
|
|
const SandboxContext& m_context;
|
|
std::unordered_set<std::string> m_runtime_grants; // Runtime-granted dangerous perms
|
|
std::chrono::steady_clock::time_point m_last_gesture;
|
|
|
|
bool CheckNormalPermission(const std::string& permission) const;
|
|
bool CheckDangerousPermission(const std::string& permission) const;
|
|
bool CheckSignaturePermission(const std::string& permission) const;
|
|
};
|
|
|
|
// Lua helper - throws error if permission denied
|
|
int RequirePermission(lua_State* L, const char* permission);
|
|
|
|
} // namespace mosis
|
|
```
|
|
|
|
### 4. Implementation
|
|
|
|
```cpp
|
|
// permission_gate.cpp
|
|
#include "permission_gate.h"
|
|
#include "lua_sandbox.h"
|
|
#include <lua.hpp>
|
|
#include <algorithm>
|
|
|
|
namespace mosis {
|
|
|
|
// Permission database
|
|
static const std::unordered_map<std::string, PermissionInfo> PERMISSIONS = {
|
|
// Normal
|
|
{"internet", {PermissionCategory::Normal, "Access the internet"}},
|
|
{"vibrate", {PermissionCategory::Normal, "Vibrate the device"}},
|
|
{"wake_lock", {PermissionCategory::Normal, "Keep device awake"}},
|
|
{"notifications", {PermissionCategory::Normal, "Show notifications"}},
|
|
|
|
// Dangerous
|
|
{"camera", {PermissionCategory::Dangerous, "Access the camera"}},
|
|
{"microphone", {PermissionCategory::Dangerous, "Record audio"}},
|
|
{"location.fine", {PermissionCategory::Dangerous, "Access precise location"}},
|
|
{"location.coarse", {PermissionCategory::Dangerous, "Access approximate location"}},
|
|
{"contacts.read", {PermissionCategory::Dangerous, "Read contacts"}},
|
|
{"contacts.write", {PermissionCategory::Dangerous, "Modify contacts"}},
|
|
{"storage.external", {PermissionCategory::Dangerous, "Access external storage"}},
|
|
{"sensors.motion", {PermissionCategory::Dangerous, "Access motion sensors"}},
|
|
{"bluetooth", {PermissionCategory::Dangerous, "Use Bluetooth"}},
|
|
|
|
// Signature
|
|
{"system.settings", {PermissionCategory::Signature, "Modify system settings"}},
|
|
{"system.install", {PermissionCategory::Signature, "Install apps"}},
|
|
{"system.admin", {PermissionCategory::Signature, "Device administrator"}},
|
|
};
|
|
|
|
PermissionGate::PermissionGate(const SandboxContext& context)
|
|
: m_context(context)
|
|
, m_last_gesture(std::chrono::steady_clock::time_point::min())
|
|
{
|
|
}
|
|
|
|
PermissionCategory PermissionGate::GetCategory(const std::string& permission) {
|
|
auto it = PERMISSIONS.find(permission);
|
|
if (it != PERMISSIONS.end()) {
|
|
return it->second.category;
|
|
}
|
|
// Unknown permissions default to Dangerous
|
|
return PermissionCategory::Dangerous;
|
|
}
|
|
|
|
bool PermissionGate::HasPermission(const std::string& permission) const {
|
|
auto category = GetCategory(permission);
|
|
|
|
switch (category) {
|
|
case PermissionCategory::Normal:
|
|
return CheckNormalPermission(permission);
|
|
case PermissionCategory::Dangerous:
|
|
return CheckDangerousPermission(permission);
|
|
case PermissionCategory::Signature:
|
|
return CheckSignaturePermission(permission);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool PermissionGate::Check(lua_State* L, const std::string& permission) {
|
|
if (!HasPermission(permission)) {
|
|
luaL_error(L, "permission denied: %s", permission.c_str());
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool PermissionGate::CheckNormalPermission(const std::string& permission) const {
|
|
// Normal permissions are auto-granted if declared in manifest
|
|
const auto& declared = m_context.permissions;
|
|
return std::find(declared.begin(), declared.end(), permission) != declared.end();
|
|
}
|
|
|
|
bool PermissionGate::CheckDangerousPermission(const std::string& permission) const {
|
|
// Must be declared in manifest
|
|
const auto& declared = m_context.permissions;
|
|
if (std::find(declared.begin(), declared.end(), permission) == declared.end()) {
|
|
return false;
|
|
}
|
|
|
|
// Must be granted at runtime (or be a system app)
|
|
if (m_context.is_system_app) {
|
|
return true;
|
|
}
|
|
|
|
return m_runtime_grants.count(permission) > 0;
|
|
}
|
|
|
|
bool PermissionGate::CheckSignaturePermission(const std::string& permission) const {
|
|
// Only system apps get signature permissions
|
|
if (!m_context.is_system_app) {
|
|
return false;
|
|
}
|
|
|
|
// Must still be declared
|
|
const auto& declared = m_context.permissions;
|
|
return std::find(declared.begin(), declared.end(), permission) != declared.end();
|
|
}
|
|
|
|
void PermissionGate::RecordUserGesture() {
|
|
m_last_gesture = std::chrono::steady_clock::now();
|
|
}
|
|
|
|
bool PermissionGate::HasRecentUserGesture(int ms) const {
|
|
auto now = std::chrono::steady_clock::now();
|
|
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - m_last_gesture);
|
|
return elapsed.count() < ms;
|
|
}
|
|
|
|
void PermissionGate::GrantPermission(const std::string& permission) {
|
|
m_runtime_grants.insert(permission);
|
|
}
|
|
|
|
void PermissionGate::RevokePermission(const std::string& permission) {
|
|
m_runtime_grants.erase(permission);
|
|
}
|
|
|
|
const std::vector<std::string>& PermissionGate::GetDeclaredPermissions() const {
|
|
return m_context.permissions;
|
|
}
|
|
|
|
std::vector<std::string> PermissionGate::GetGrantedPermissions() const {
|
|
std::vector<std::string> granted;
|
|
|
|
for (const auto& perm : m_context.permissions) {
|
|
if (HasPermission(perm)) {
|
|
granted.push_back(perm);
|
|
}
|
|
}
|
|
|
|
return granted;
|
|
}
|
|
|
|
int RequirePermission(lua_State* L, const char* permission) {
|
|
// Get sandbox from registry
|
|
lua_getfield(L, LUA_REGISTRYINDEX, "__mosis_sandbox");
|
|
auto* sandbox = static_cast<LuaSandbox*>(lua_touserdata(L, -1));
|
|
lua_pop(L, 1);
|
|
|
|
if (!sandbox) {
|
|
return luaL_error(L, "sandbox not initialized");
|
|
}
|
|
|
|
// TODO: Get PermissionGate from sandbox once integrated
|
|
// For now, this is a placeholder
|
|
return 0;
|
|
}
|
|
|
|
} // namespace mosis
|
|
```
|
|
|
|
---
|
|
|
|
## Test Cases
|
|
|
|
### Test 1: Normal Permission Auto-Granted
|
|
|
|
**Script**: `scripts/test_permission_normal.lua`
|
|
```lua
|
|
-- Test that normal permissions are auto-granted when declared
|
|
-- This is called from C++ which sets up context with "internet" permission
|
|
|
|
-- If we get here, permission check passed
|
|
print("PASS: Normal permission granted")
|
|
```
|
|
|
|
**C++ Test**:
|
|
```cpp
|
|
bool Test_NormalPermissionAutoGranted(std::string& error_msg) {
|
|
SandboxContext ctx = TestContext();
|
|
ctx.permissions = {"internet"}; // Declare normal permission
|
|
|
|
LuaSandbox sandbox(ctx);
|
|
PermissionGate gate(ctx);
|
|
|
|
// Normal permissions should be auto-granted
|
|
EXPECT_TRUE(gate.HasPermission("internet"));
|
|
|
|
return true;
|
|
}
|
|
```
|
|
|
|
### Test 2: Dangerous Permission Requires Grant
|
|
|
|
**C++ Test**:
|
|
```cpp
|
|
bool Test_DangerousPermissionRequiresGrant(std::string& error_msg) {
|
|
SandboxContext ctx = TestContext();
|
|
ctx.permissions = {"camera"}; // Declare dangerous permission
|
|
|
|
PermissionGate gate(ctx);
|
|
|
|
// Not granted yet
|
|
EXPECT_FALSE(gate.HasPermission("camera"));
|
|
|
|
// Grant at runtime
|
|
gate.GrantPermission("camera");
|
|
|
|
// Now should have it
|
|
EXPECT_TRUE(gate.HasPermission("camera"));
|
|
|
|
// Revoke
|
|
gate.RevokePermission("camera");
|
|
EXPECT_FALSE(gate.HasPermission("camera"));
|
|
|
|
return true;
|
|
}
|
|
```
|
|
|
|
### Test 3: Signature Permission System Only
|
|
|
|
**C++ Test**:
|
|
```cpp
|
|
bool Test_SignaturePermissionSystemOnly(std::string& error_msg) {
|
|
// Non-system app
|
|
SandboxContext ctx = TestContext();
|
|
ctx.permissions = {"system.settings"};
|
|
ctx.is_system_app = false;
|
|
|
|
PermissionGate gate(ctx);
|
|
EXPECT_FALSE(gate.HasPermission("system.settings"));
|
|
|
|
// System app
|
|
SandboxContext sys_ctx = TestContext();
|
|
sys_ctx.permissions = {"system.settings"};
|
|
sys_ctx.is_system_app = true;
|
|
|
|
PermissionGate sys_gate(sys_ctx);
|
|
EXPECT_TRUE(sys_gate.HasPermission("system.settings"));
|
|
|
|
return true;
|
|
}
|
|
```
|
|
|
|
### Test 4: User Gesture Tracking
|
|
|
|
**C++ Test**:
|
|
```cpp
|
|
bool Test_UserGestureRequired(std::string& error_msg) {
|
|
SandboxContext ctx = TestContext();
|
|
PermissionGate gate(ctx);
|
|
|
|
// No recent gesture
|
|
EXPECT_FALSE(gate.HasRecentUserGesture(5000));
|
|
|
|
// Record gesture
|
|
gate.RecordUserGesture();
|
|
|
|
// Should have recent gesture
|
|
EXPECT_TRUE(gate.HasRecentUserGesture(5000));
|
|
|
|
return true;
|
|
}
|
|
```
|
|
|
|
### Test 5: Undeclared Permission Denied
|
|
|
|
**C++ Test**:
|
|
```cpp
|
|
bool Test_UndeclaredPermissionDenied(std::string& error_msg) {
|
|
SandboxContext ctx = TestContext();
|
|
ctx.permissions = {}; // No permissions declared
|
|
|
|
PermissionGate gate(ctx);
|
|
|
|
// Even normal permissions need to be declared
|
|
EXPECT_FALSE(gate.HasPermission("internet"));
|
|
|
|
return true;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Build & Test Commands
|
|
|
|
### Build
|
|
|
|
```bash
|
|
cd sandbox-test
|
|
cmake -B build -DCMAKE_TOOLCHAIN_FILE=%VCPKG_ROOT%/scripts/buildsystems/vcpkg.cmake
|
|
cmake --build build --config Debug
|
|
```
|
|
|
|
### Run All Tests
|
|
|
|
```bash
|
|
./build/Debug/sandbox-test.exe
|
|
```
|
|
|
|
### Run Permission Tests Only
|
|
|
|
```bash
|
|
./build/Debug/sandbox-test.exe --test Permission
|
|
```
|
|
|
|
---
|
|
|
|
## Acceptance Criteria
|
|
|
|
All tests must pass:
|
|
|
|
- [x] `Test_NormalPermissionAutoGranted` - Normal perms auto-granted when declared
|
|
- [x] `Test_DangerousPermissionRequiresGrant` - Dangerous perms need runtime grant
|
|
- [x] `Test_SignaturePermissionSystemOnly` - Signature perms only for system apps
|
|
- [x] `Test_UserGestureTracking` - User gesture tracking works
|
|
- [x] `Test_UndeclaredPermissionDenied` - Undeclared perms always denied
|
|
- [x] `Test_SystemAppGetsDangerousAuto` - System apps get dangerous perms auto
|
|
- [x] `Test_PermissionCategoryCheck` - Permission categories are correct
|
|
|
|
---
|
|
|
|
## Integration Notes
|
|
|
|
After Milestone 2:
|
|
1. Add `PermissionGate` to `LuaSandbox` class
|
|
2. Call `RequirePermission()` before sensitive operations
|
|
3. Wire up user gesture recording from touch events
|
|
|
|
---
|
|
|
|
## Next Steps
|
|
|
|
After Milestone 2 passes:
|
|
1. Milestone 3: Audit Logging & Rate Limiting
|
|
2. Use permission gate in all subsequent API implementations
|