-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 2754e43
Showing
10 changed files
with
551 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
.idea/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
Copyright (c) 2018 Lars Wilhelmsen <[email protected]> | ||
|
||
Permission is hereby granted, free of charge, to any | ||
person obtaining a copy of this software and associated | ||
documentation files (the "Software"), to deal in the | ||
Software without restriction, including without | ||
limitation the rights to use, copy, modify, merge, | ||
publish, distribute, sublicense, and/or sell copies of | ||
the Software, and to permit persons to whom the Software | ||
is furnished to do so, subject to the following | ||
conditions: | ||
|
||
The above copyright notice and this permission notice | ||
shall be included in all copies or substantial portions | ||
of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF | ||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED | ||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A | ||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT | ||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | ||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | ||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR | ||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | ||
DEALINGS IN THE SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
test: | ||
go test -v |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
# Accumulo Access Expressions for Go | ||
|
||
## Introduction | ||
|
||
This package provides a simple way to parse and evaluate Accumulo access expressions in Go, based on the [AccessExpression specification](https://github.com/apache/accumulo-access/blob/main/SPECIFICATION.md). | ||
|
||
## Usage | ||
|
||
```go | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
accumulo "github.com/larsw/accumulo-access-go/pkg" | ||
) | ||
|
||
func main() { | ||
res, err := accumulo.CheckAuthorization("A & B & (C | D)", "A,B,C") | ||
if err != nil { | ||
fmt.Printf("err: %v\n", err) | ||
return | ||
} | ||
// Print the result | ||
fmt.Printf("%v\n", res) | ||
} | ||
``` | ||
|
||
* Lars Wilhelmsen (https://github.com/larsw/) | ||
|
||
## License | ||
|
||
Licensed under the MIT License [LICENSE_MIT](LICENSE). | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
module github.com/larsw/accumulo-access-go | ||
|
||
go 1.18 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
// Package pkg Copyright 2024 Lars Wilhelmsen <[email protected]>. All rights reserved. | ||
// Use of this source code is governed by the MIT license that can be found in the LICENSE file. | ||
package pkg | ||
|
||
import "strings" | ||
|
||
// CheckAuthorization checks if the given authorizations are allowed to perform the given expression. | ||
// Arguments: | ||
// | ||
// expression: The expression to check. | ||
// authorizations: A comma-separated list of authorizations. | ||
// | ||
// Returns: | ||
// | ||
// True if the authorizations are allowed to perform the expression, false otherwise. | ||
func CheckAuthorization(expression string, authorizations string) (bool, error) { | ||
parser := NewParser(newLexer(expression)) | ||
ast, err := parser.Parse() | ||
if err != nil { | ||
return false, err | ||
} | ||
authorizationMap := make(map[string]bool) | ||
for _, authorization := range strings.Split(authorizations, ",") { | ||
authorizationMap[authorization] = true | ||
} | ||
return ast.Evaluate(authorizationMap), nil | ||
} | ||
|
||
// PrepareAuthorizationCheck returns a function that can be used to check if the given authorizations are allowed to perform the given expression. | ||
// Arguments: | ||
// | ||
// authorizations: A comma-separated list of authorizations. | ||
// | ||
// Returns: | ||
// | ||
// A function that can be used to check if the given authorizations are allowed to perform the given expression. | ||
func PrepareAuthorizationCheck(authorizations string) func(string) (bool, error) { | ||
authorizationMap := make(map[string]bool) | ||
for _, authorization := range strings.Split(authorizations, ",") { | ||
authorizationMap[authorization] = true | ||
} | ||
return func(expression string) (bool, error) { | ||
parser := NewParser(newLexer(expression)) | ||
ast, err := parser.Parse() | ||
if err != nil { | ||
return false, err | ||
} | ||
return ast.Evaluate(authorizationMap), nil | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package pkg | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
) | ||
|
||
type testCase struct { | ||
expression string | ||
authorizations string | ||
expected bool | ||
} | ||
|
||
func TestCheckAuthorization(t *testing.T) { | ||
testCases := []testCase{ | ||
{"label1", "label1", true}, | ||
{"label1|label2", "label1", true}, | ||
{"label1&label2", "label1", false}, | ||
{"label1&label2", "label1,label2", true}, | ||
{"label1&(label2 | label3)", "label1", false}, | ||
{"label1&(label2 | label3)", "label1,label3", true}, | ||
{"label1&(label2 | label3)", "label1,label2", true}, | ||
{"(label2 | label3)", "label1", false}, | ||
{"(label2 | label3)", "label2", true}, | ||
{"(label2 & label3)", "label2", false}, | ||
{"((label2 | label3))", "label2", true}, | ||
{"((label2 & label3))", "label2", false}, | ||
{"(((((label2 & label3)))))", "label2", false}, | ||
{"(a & b) & (c & d)", "a,b,c,d", true}, | ||
{"(a & b) & (c & d)", "a,b,c", false}, | ||
{"(a & b) | (c & d)", "a,b,d", true}, | ||
{"(a | b) & (c | d)", "a,d", true}, | ||
{"\"a b c\"", "\"a b c\"", true}, | ||
} | ||
|
||
for _, tc := range testCases { | ||
t.Run(fmt.Sprintf("\"%v\" + \"%v\" -> %v", tc.expression, tc.authorizations, tc.expected), func(t *testing.T) { | ||
result, err := CheckAuthorization(tc.expression, tc.authorizations) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if result != tc.expected { | ||
t.Fatalf("expected %v for %s with %s", tc.expected, tc.expression, tc.authorizations) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
// Package pkg Copyright 2024 Lars Wilhelmsen <[email protected]>. All rights reserved. | ||
// Use of this source code is governed by the MIT license that can be found in the LICENSE file. | ||
package pkg | ||
|
||
import ( | ||
"fmt" | ||
"unicode" | ||
) | ||
|
||
// Token represents the different types of tokens. | ||
type Token int | ||
|
||
// Define token constants. | ||
const ( | ||
AccessToken Token = iota | ||
OpenParen | ||
CloseParen | ||
And | ||
Or | ||
None | ||
Error | ||
) | ||
|
||
//func (t Token) String() string { | ||
// switch t { | ||
// case AccessToken: | ||
// return "AccessToken" | ||
// case OpenParen: | ||
// return "(" | ||
// case CloseParen: | ||
// return ")" | ||
// case And: | ||
// return "&" | ||
// case Or: | ||
// return "|" | ||
// case None: | ||
// return "None" | ||
// default: | ||
// return "Unknown" | ||
// } | ||
//} | ||
|
||
// Lexer represents a lexer for tokenizing strings. | ||
type Lexer struct { | ||
input string | ||
pos int | ||
readPos int | ||
ch byte | ||
} | ||
|
||
// newLexer creates a new Lexer instance. | ||
func newLexer(input string) *Lexer { | ||
l := &Lexer{input: input} | ||
l.readChar() | ||
return l | ||
} | ||
|
||
// LexerError represents an error that occurred during lexing. | ||
type LexerError struct { | ||
Char byte | ||
Position int | ||
} | ||
|
||
func (e LexerError) Error() string { | ||
return fmt.Sprintf("unexpected character '%c' at position %d", e.Char, e.Position) | ||
} | ||
|
||
func (l *Lexer) readChar() { | ||
if l.readPos >= len(l.input) { | ||
l.ch = 0 | ||
} else { | ||
l.ch = l.input[l.readPos] | ||
} | ||
l.pos = l.readPos | ||
l.readPos++ | ||
} | ||
|
||
func (l *Lexer) peekChar() byte { | ||
if l.readPos >= len(l.input) { | ||
return 0 | ||
} | ||
return l.input[l.readPos] | ||
} | ||
|
||
func (l *Lexer) nextToken() (Token, string, error) { | ||
l.skipWhitespace() | ||
|
||
switch l.ch { | ||
case '(': | ||
l.readChar() | ||
return OpenParen, "", nil | ||
case ')': | ||
l.readChar() | ||
return CloseParen, "", nil | ||
case '&': | ||
l.readChar() | ||
return And, "", nil | ||
case '|': | ||
l.readChar() | ||
return Or, "", nil | ||
case 0: | ||
return None, "", nil | ||
case '"': | ||
strLiteral := l.readString() | ||
return AccessToken, strLiteral, nil | ||
default: | ||
if isLegalTokenLetter(l.ch) { | ||
val := l.readIdentifier() | ||
return AccessToken, val, nil | ||
} else { | ||
return Error, "", LexerError{Char: l.ch, Position: l.pos} | ||
} | ||
} | ||
} | ||
|
||
func (l *Lexer) readIdentifier() string { | ||
startPos := l.pos | ||
for isLegalTokenLetter(l.ch) { | ||
l.readChar() | ||
} | ||
return l.input[startPos:l.pos] | ||
} | ||
|
||
func (l *Lexer) readString() string { | ||
startPos := l.pos + 1 // Skip initial double quote | ||
for { | ||
l.readChar() | ||
if l.ch == '"' || l.ch == 0 { | ||
break // End of string or end of input | ||
} | ||
|
||
// Handle escape sequences | ||
if l.ch == '\\' { | ||
l.readChar() | ||
if l.ch != '"' && l.ch != '\\' { | ||
// Handle invalid escape sequence | ||
return l.input[startPos : l.pos-1] // Return string up to invalid escape | ||
} | ||
} | ||
} | ||
str := l.input[startPos:l.pos] | ||
l.readChar() // Skip closing double quote | ||
return str | ||
} | ||
|
||
func (l *Lexer) skipWhitespace() { | ||
for l.ch == ' ' || l.ch == '\t' || l.ch == '\n' || l.ch == '\r' { | ||
l.readChar() | ||
} | ||
} | ||
|
||
func isLegalTokenLetter(ch byte) bool { | ||
|
||
return unicode.IsLetter(rune(ch)) || | ||
unicode.IsDigit(rune(ch)) || | ||
ch == '_' || | ||
ch == '-' || | ||
ch == '.' || | ||
ch == ':' | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package pkg | ||
|
||
import "testing" | ||
|
||
func TestLexer(t *testing.T) { | ||
lexer := newLexer("(a & b) | c") | ||
token, _, _ := lexer.nextToken() | ||
if token != OpenParen { | ||
t.Fatal("expected OpenParen") | ||
} | ||
token, val, _ := lexer.nextToken() | ||
if token != AccessToken { | ||
t.Fatal("expected AccessToken") | ||
} | ||
if val != "a" { | ||
t.Fatal("expected a") | ||
} | ||
token, _, _ = lexer.nextToken() | ||
if token != And { | ||
t.Fatal("expected And") | ||
} | ||
token, val, _ = lexer.nextToken() | ||
if token != AccessToken { | ||
t.Fatal("expected AccessToken") | ||
} | ||
if val != "b" { | ||
t.Fatal("expected b") | ||
} | ||
token, _, _ = lexer.nextToken() | ||
if token != CloseParen { | ||
t.Fatal("expected CloseParen") | ||
} | ||
token, _, _ = lexer.nextToken() | ||
if token != Or { | ||
t.Fatal("expected Or") | ||
} | ||
token, val, _ = lexer.nextToken() | ||
if token != AccessToken { | ||
t.Fatal("expected AccessToken") | ||
} | ||
if val != "c" { | ||
t.Fatal("expected c") | ||
} | ||
token, _, _ = lexer.nextToken() | ||
if token != None { | ||
t.Fatal("expected end of input") | ||
} | ||
} |
Oops, something went wrong.