Files
MosisService/docs/SANDBOX_MILESTONE_2.md

14 KiB

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

// 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

// 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

// 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

// 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

-- 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:

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:

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:

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:

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:

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

cd sandbox-test
cmake -B build -DCMAKE_TOOLCHAIN_FILE=%VCPKG_ROOT%/scripts/buildsystems/vcpkg.cmake
cmake --build build --config Debug

Run All Tests

./build/Debug/sandbox-test.exe

Run Permission Tests Only

./build/Debug/sandbox-test.exe --test Permission

Acceptance Criteria

All tests must pass:

  • Test_NormalPermissionAutoGranted - Normal perms auto-granted when declared
  • Test_DangerousPermissionRequiresGrant - Dangerous perms need runtime grant
  • Test_SignaturePermissionSystemOnly - Signature perms only for system apps
  • Test_UserGestureTracking - User gesture tracking works
  • Test_UndeclaredPermissionDenied - Undeclared perms always denied
  • Test_SystemAppGetsDangerousAuto - System apps get dangerous perms auto
  • 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