@@ -4,6 +4,7 @@ package parser
44import (
55 "fmt"
66 "slices"
7+ "strings"
78
89 "github.com/cedar-policy/cedar-go/types"
910 "github.com/cedar-policy/cedar-go/x/exp/schema/ast"
@@ -86,6 +87,8 @@ func tokenName(tt tokenType) string {
8687 return "'?'"
8788 case tokenEquals :
8889 return "'='"
90+ case tokenReservedKeyword :
91+ return "reserved keyword"
8992 default :
9093 return "unknown"
9194 }
@@ -99,6 +102,8 @@ func tokenDesc(tok token) string {
99102 return fmt .Sprintf ("identifier %q" , tok .Text )
100103 case tokenString :
101104 return fmt .Sprintf ("string %q" , tok .Text )
105+ case tokenReservedKeyword :
106+ return fmt .Sprintf ("reserved keyword %q" , tok .Text )
102107 default :
103108 return fmt .Sprintf ("%q" , tok .Text )
104109 }
@@ -145,6 +150,9 @@ func (p *parser) parseNamespace(annotations ast.Annotations) (parsedNamespace, e
145150 if err != nil {
146151 return parsedNamespace {}, err
147152 }
153+ if slices .Contains (strings .Split (string (path ), "::" ), "__cedar" ) {
154+ return parsedNamespace {}, fmt .Errorf ("%s: the name %q contains \" __cedar\" , which is reserved" , p .tok .Pos , path )
155+ }
148156 if err := p .expect (tokenLBrace ); err != nil {
149157 return parsedNamespace {}, err
150158 }
@@ -213,7 +221,7 @@ func (p *parser) parseEntity(annotations ast.Annotations, schema *ast.Schema) er
213221
214222 // Parse optional 'in' clause
215223 var memberOf []ast.EntityTypeRef
216- if p .tok .Type == tokenIdent && p .tok .Text == "in" {
224+ if p .tok .Type == tokenReservedKeyword && p .tok .Text == "in" {
217225 if err := p .readToken (); err != nil {
218226 return err
219227 }
@@ -333,7 +341,7 @@ func (p *parser) parseAction(annotations ast.Annotations, schema *ast.Schema) er
333341
334342 // Parse optional 'in' clause
335343 var memberOf []ast.ParentRef
336- if p .tok .Type == tokenIdent && p .tok .Text == "in" {
344+ if p .tok .Type == tokenReservedKeyword && p .tok .Text == "in" {
337345 if err := p .readToken (); err != nil {
338346 return err
339347 }
@@ -431,7 +439,7 @@ func (p *parser) parseAnnotations() (ast.Annotations, error) {
431439 if err := p .readToken (); err != nil {
432440 return nil , err
433441 }
434- if p .tok .Type != tokenIdent {
442+ if p .tok .Type != tokenIdent && p . tok . Type != tokenReservedKeyword {
435443 return nil , p .errorf ("expected annotation name, got %s" , tokenDesc (p .tok ))
436444 }
437445 key := types .Ident (p .tok .Text )
@@ -459,6 +467,9 @@ func (p *parser) parseAnnotations() (ast.Annotations, error) {
459467 if annotations == nil {
460468 annotations = ast.Annotations {}
461469 }
470+ if _ , ok := annotations [key ]; ok {
471+ return nil , p .errorf ("duplicate annotation %q" , key )
472+ }
462473 if hasValue {
463474 annotations [key ] = value
464475 } else {
@@ -469,8 +480,10 @@ func (p *parser) parseAnnotations() (ast.Annotations, error) {
469480}
470481
471482// parsePath parses IDENT { '::' IDENT }
483+ // As a special case, "__cedar" is accepted as the first component even though it is
484+ // a reserved keyword, because it is valid as a type reference prefix (e.g. __cedar::String).
472485func (p * parser ) parsePath () (types.Path , error ) {
473- if p .tok .Type != tokenIdent {
486+ if p .tok .Type != tokenIdent && ( p . tok . Type != tokenReservedKeyword || p . tok . Text != "__cedar" ) {
474487 return "" , p .errorf ("expected identifier, got %s" , tokenDesc (p .tok ))
475488 }
476489 path := p .tok .Text
@@ -494,8 +507,10 @@ func (p *parser) parsePath() (types.Path, error) {
494507
495508// parsePathForRef parses a path that may include a trailing '::' followed by a string literal
496509// for action parent references. Returns the path and whether a string was found.
510+ // As a special case, "__cedar" is accepted as the first component even though it is
511+ // a reserved keyword, because it is valid as a type reference prefix (e.g. __cedar::String).
497512func (p * parser ) parsePathForRef () (path types.Path , str types.String , qualified bool , err error ) {
498- if p .tok .Type != tokenIdent {
513+ if p .tok .Type != tokenIdent && ( p . tok . Type != tokenReservedKeyword || p . tok . Text != "__cedar" ) {
499514 return "" , "" , false , p .errorf ("expected identifier, got %s" , tokenDesc (p .tok ))
500515 }
501516 pathStr := p .tok .Text
@@ -570,14 +585,17 @@ func (p *parser) parseNames() ([]types.String, error) {
570585}
571586
572587func (p * parser ) parseName () (types.String , error ) {
573- switch p .tok .Type {
574- case tokenIdent :
588+ // Weirdly, Cedar schemas allow __cedar as an attribute or action name without
589+ // double quotes, while all other reserved keywords require double quotes
590+ switch {
591+ case p .tok .Type == tokenIdent ,
592+ p .tok .Type == tokenReservedKeyword && p .tok .Text == "__cedar" :
575593 name := types .String (p .tok .Text )
576594 if err := p .readToken (); err != nil {
577595 return "" , err
578596 }
579597 return name , nil
580- case tokenString :
598+ case p . tok . Type == tokenString :
581599 name := types .String (p .tok .Text )
582600 if err := p .readToken (); err != nil {
583601 return "" , err
@@ -674,6 +692,9 @@ func (p *parser) parseAppliesTo() (*ast.AppliesTo, error) {
674692 return nil , err
675693 }
676694 at := & ast.AppliesTo {}
695+ hasPrincipal := false
696+ hasResource := false
697+ hasContext := false
677698 for p .tok .Type != tokenRBrace {
678699 if p .tok .Type == tokenEOF {
679700 return nil , p .errorf ("expected '}' to close appliesTo, got EOF" )
@@ -683,6 +704,10 @@ func (p *parser) parseAppliesTo() (*ast.AppliesTo, error) {
683704 }
684705 switch p .tok .Text {
685706 case "principal" :
707+ if hasPrincipal {
708+ return nil , p .errorf ("duplicate principal declaration in appliesTo" )
709+ }
710+ hasPrincipal = true
686711 if err := p .readToken (); err != nil {
687712 return nil , err
688713 }
@@ -693,8 +718,15 @@ func (p *parser) parseAppliesTo() (*ast.AppliesTo, error) {
693718 if err != nil {
694719 return nil , err
695720 }
721+ if len (refs ) == 0 {
722+ return nil , p .errorf ("principal types must not be empty" )
723+ }
696724 at .Principals = refs
697725 case "resource" :
726+ if hasResource {
727+ return nil , p .errorf ("duplicate resource declaration in appliesTo" )
728+ }
729+ hasResource = true
698730 if err := p .readToken (); err != nil {
699731 return nil , err
700732 }
@@ -705,8 +737,15 @@ func (p *parser) parseAppliesTo() (*ast.AppliesTo, error) {
705737 if err != nil {
706738 return nil , err
707739 }
740+ if len (refs ) == 0 {
741+ return nil , p .errorf ("resource types must not be empty" )
742+ }
708743 at .Resources = refs
709744 case "context" :
745+ if hasContext {
746+ return nil , p .errorf ("duplicate context declaration in appliesTo" )
747+ }
748+ hasContext = true
710749 if err := p .readToken (); err != nil {
711750 return nil , err
712751 }
@@ -727,6 +766,12 @@ func (p *parser) parseAppliesTo() (*ast.AppliesTo, error) {
727766 }
728767 }
729768 }
769+ if ! hasPrincipal {
770+ return nil , p .errorf ("appliesTo must include a principal declaration" )
771+ }
772+ if ! hasResource {
773+ return nil , p .errorf ("appliesTo must include a resource declaration" )
774+ }
730775 return at , p .readToken () // consume '}'
731776}
732777
0 commit comments