...
This commit is contained in:
@@ -13,6 +13,7 @@ pub struct Atlas {
|
||||
pub mut:
|
||||
name string
|
||||
collections map[string]&Collection
|
||||
groups map[string]&Group // name -> Group mapping
|
||||
}
|
||||
|
||||
@[params]
|
||||
@@ -129,3 +130,32 @@ pub fn (mut a Atlas) fix_links() ! {
|
||||
col.fix_links()!
|
||||
}
|
||||
}
|
||||
|
||||
// Add a group to the atlas
|
||||
pub fn (mut a Atlas) group_add(mut group Group) ! {
|
||||
if group.name in a.groups {
|
||||
return error('Group ${group.name} already exists')
|
||||
}
|
||||
a.groups[group.name] = &group
|
||||
}
|
||||
|
||||
// Get a group by name
|
||||
pub fn (a Atlas) group_get(name string) !&Group {
|
||||
name_lower := texttools.name_fix(name)
|
||||
return a.groups[name_lower] or { return error('Group ${name} not found') }
|
||||
}
|
||||
|
||||
// Get all groups matching a session's email
|
||||
pub fn (a Atlas) groups_get(session Session) []&Group {
|
||||
mut matching := []&Group{}
|
||||
|
||||
email_lower := session.email.to_lower()
|
||||
|
||||
for _, group in a.groups {
|
||||
if group.matches(email_lower) {
|
||||
matching << group
|
||||
}
|
||||
}
|
||||
|
||||
return matching
|
||||
}
|
||||
|
||||
@@ -3,9 +3,17 @@ module atlas
|
||||
import incubaid.herolib.core.pathlib
|
||||
import incubaid.herolib.core.texttools
|
||||
import incubaid.herolib.core.base
|
||||
import incubaid.herolib.data.paramsparser { Params }
|
||||
import incubaid.herolib.ui.console
|
||||
import os
|
||||
|
||||
pub struct Session {
|
||||
pub mut:
|
||||
user string // username
|
||||
email string // user's email (lowercase internally)
|
||||
params Params // additional context from request/webserver
|
||||
}
|
||||
|
||||
@[heap]
|
||||
pub struct Collection {
|
||||
pub mut:
|
||||
@@ -20,6 +28,8 @@ pub mut:
|
||||
git_url string // NEW: URL to the git repository for editing
|
||||
git_branch string // NEW: Git branch for this collection
|
||||
git_edit_url string @[skip]
|
||||
acl_read []string // Group names allowed to read (lowercase)
|
||||
acl_write []string // Group names allowed to write (lowercase)
|
||||
}
|
||||
|
||||
@[params]
|
||||
@@ -241,7 +251,7 @@ pub fn (mut c Collection) error(args CollectionErrorArgs) {
|
||||
|
||||
// Check if this error was already reported
|
||||
if hash in c.error_cache {
|
||||
return // Skip duplicate
|
||||
return
|
||||
}
|
||||
|
||||
// Mark this error as reported
|
||||
@@ -321,3 +331,47 @@ pub fn (mut c Collection) fix_links() ! {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if session can read this collection
|
||||
pub fn (c Collection) can_read(session Session) bool {
|
||||
// If no ACL set, everyone can read
|
||||
if c.acl_read.len == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
// Get user's groups
|
||||
mut atlas := c.atlas
|
||||
groups := atlas.groups_get(session)
|
||||
group_names := groups.map(it.name)
|
||||
|
||||
// Check if any of user's groups are in read ACL
|
||||
for acl_group in c.acl_read {
|
||||
if acl_group in group_names {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if session can write this collection
|
||||
pub fn (c Collection) can_write(session Session) bool {
|
||||
// If no ACL set, no one can write
|
||||
if c.acl_write.len == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// Get user's groups
|
||||
mut atlas := c.atlas
|
||||
groups := atlas.groups_get(session)
|
||||
group_names := groups.map(it.name)
|
||||
|
||||
// Check if any of user's groups are in write ACL
|
||||
for acl_group in c.acl_write {
|
||||
if acl_group in group_names {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ pub enum CollectionErrorCategory {
|
||||
file_not_found
|
||||
invalid_collection
|
||||
general_error
|
||||
acl_denied // NEW: Access denied by ACL
|
||||
}
|
||||
|
||||
pub struct CollectionError {
|
||||
@@ -57,5 +58,6 @@ pub fn (e CollectionError) category_str() string {
|
||||
.file_not_found { 'File Not Found' }
|
||||
.invalid_collection { 'Invalid Collection' }
|
||||
.general_error { 'General Error' }
|
||||
.acl_denied { 'ACL Access Denied' }
|
||||
}
|
||||
}
|
||||
62
lib/data/atlas/group.v
Normal file
62
lib/data/atlas/group.v
Normal file
@@ -0,0 +1,62 @@
|
||||
module atlas
|
||||
|
||||
import incubaid.herolib.core.texttools
|
||||
|
||||
@[heap]
|
||||
pub struct Group {
|
||||
pub mut:
|
||||
name string // normalized to lowercase
|
||||
patterns []string // email patterns, normalized to lowercase
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct GroupNewArgs {
|
||||
pub mut:
|
||||
name string @[required]
|
||||
patterns []string @[required]
|
||||
}
|
||||
|
||||
// Create a new Group
|
||||
pub fn new_group(args GroupNewArgs) !Group {
|
||||
mut name := texttools.name_fix(args.name)
|
||||
mut patterns := args.patterns.map(it.to_lower())
|
||||
|
||||
return Group{
|
||||
name: name
|
||||
patterns: patterns
|
||||
}
|
||||
}
|
||||
|
||||
// Check if email matches any pattern in this group
|
||||
pub fn (g Group) matches(email string) bool {
|
||||
email_lower := email.to_lower()
|
||||
|
||||
for pattern in g.patterns {
|
||||
if matches_pattern(email_lower, pattern) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Helper: match email against wildcard pattern
|
||||
// '*@domain.com' matches 'user@domain.com'
|
||||
// 'exact@email.com' matches only 'exact@email.com'
|
||||
fn matches_pattern(email string, pattern string) bool {
|
||||
if pattern == '*' {
|
||||
return true
|
||||
}
|
||||
|
||||
if !pattern.contains('*') {
|
||||
return email == pattern
|
||||
}
|
||||
|
||||
// Handle wildcard patterns like '*@domain.com'
|
||||
if pattern.starts_with('*') {
|
||||
suffix := pattern[1..] // Remove the '*'
|
||||
return email.ends_with(suffix)
|
||||
}
|
||||
|
||||
// Could add more complex patterns here if needed
|
||||
return false
|
||||
}
|
||||
@@ -95,6 +95,7 @@ fn should_skip_dir(entry pathlib.Path) bool {
|
||||
// Scan collection directory for files
|
||||
fn (mut c Collection) scan() ! {
|
||||
c.scan_path(mut c.path)!
|
||||
c.scan_acl()! // NEW: scan ACL files
|
||||
c.detect_git_url() or {
|
||||
console.print_debug('Could not detect git URL for collection ${c.name}: ${err}')
|
||||
}
|
||||
@@ -133,3 +134,28 @@ fn (mut c Collection) scan_path(mut dir pathlib.Path) ! {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Scan for ACL files
|
||||
fn (mut c Collection) scan_acl() ! {
|
||||
// Look for read.acl in collection directory
|
||||
read_acl_path := '${c.path.path}/read.acl'
|
||||
if os.exists(read_acl_path) {
|
||||
content := os.read_file(read_acl_path)!
|
||||
// Split by newlines and normalize
|
||||
c.acl_read = content.split('\n')
|
||||
.map(it.trim_space())
|
||||
.filter(it.len > 0)
|
||||
.map(it.to_lower())
|
||||
}
|
||||
|
||||
// Look for write.acl in collection directory
|
||||
write_acl_path := '${c.path.path}/write.acl'
|
||||
if os.exists(write_acl_path) {
|
||||
content := os.read_file(write_acl_path)!
|
||||
// Split by newlines and normalize
|
||||
c.acl_write = content.split('\n')
|
||||
.map(it.trim_space())
|
||||
.filter(it.len > 0)
|
||||
.map(it.to_lower())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user