Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@ required:
properties:
path:
type: string
description: The path for which access is requested.
description: |
The path for which access is requested.

Any characters which are considered special in reply path patterns will
be escaped by a backslash ('\') character. That way, the "path" field in the prompt
can be used directly as the "path-pattern" field in a reply and exactly
match the requested path.
example: /home/ubuntu/Downloads/image.png
requested-permissions:
type: array
Expand Down
36 changes: 36 additions & 0 deletions interfaces/prompting/patterns/patterns_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,42 @@ type patternsSuite struct{}

var _ = Suite(&patternsSuite{})

func (s *patternsSuite) TestEscapeLiteralPathMatchesOriginalPath(c *C) {
for _, testCase := range []struct {
original string
expected string
}{
{`/foo/bar`, `/foo/bar`},
{`/foo*bar`, `/foo\*bar`},
{`/foo?bar`, `/foo\?bar`},
{`/foo\bar`, `/foo\\bar`},
{`/foo(bar,baz)`, `/foo(bar,baz)`}, // () are not special, so not escaped
{`/foo[bar,baz]`, `/foo\[bar,baz\]`},
{`/foo{bar,baz}`, `/foo\{bar,baz\}`},
{`/foo\*bar`, `/foo\\\*bar`},
{`/foo\?bar`, `/foo\\\?bar`},
{`/foo\\bar`, `/foo\\\\bar`},
{`/foo\(bar,baz\)`, `/foo\\(bar,baz\\)`}, // () are not special, so not escaped
{`/foo\[bar,baz\]`, `/foo\\\[bar,baz\\\]`},
{`/foo\{bar,baz\}`, `/foo\\\{bar,baz\\\}`},
{`/foo*?()[]{}'",\`, `/foo\*\?()\[\]\{\}'",\\`},
{`/foo/bar/[アニメ][ゲーム動画].mkv`, `/foo/bar/\[アニメ\]\[ゲーム動画\].mkv`},
} {
result := patterns.EscapeLiteralPath(testCase.original)
c.Check(result, Equals, testCase.expected)

// Check that a path pattern equal to the escaped path does indeed
// match the original path
expectedPathPattern, err := patterns.ParsePathPattern(testCase.expected)
c.Assert(err, IsNil, Commentf("testCase: %+v", testCase))
c.Assert(expectedPathPattern, NotNil, Commentf("testCase: %+v", testCase))
c.Check(expectedPathPattern.String(), Equals, testCase.expected, Commentf("testCase: %+v", testCase))
matches, err := expectedPathPattern.Match(testCase.original)
c.Check(err, IsNil, Commentf("testCase: %+v", testCase))
c.Check(matches, Equals, true, Commentf("testCase: %+v", testCase))
}
}

func (s *patternsSuite) TestParsePathPatternHappy(c *C) {
for _, pattern := range []string{
"/",
Expand Down
11 changes: 9 additions & 2 deletions interfaces/prompting/patterns/variant.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,14 +179,21 @@ func (c component) componentRegex() string {

var escapeFinder = regexp.MustCompile(`\\(.)`)

// unescapeLiteral removes any `\` characters which are used to escape another
// character. Note that escaped `\` characters are not removed, since they are
// unescapeLiteral removes any '\' characters which are used to escape another
// character. Note that escaped '\' characters are not removed, since they are
// not acting as an escape character in those instances. That is, `\\` is
// reduced to `\`.
func unescapeLiteral(literal string) string {
return escapeFinder.ReplaceAllString(literal, "${1}")
}

var escaper = regexp.MustCompile(`([\*\?\[\]\{\}\\])`)

// EscapeLiteralPath escapes any special characters from the given path.
func EscapeLiteralPath(path string) string {
return escaper.ReplaceAllString(path, `\${1}`)
}

type PatternVariant struct {
variant string
components []component
Expand Down
20 changes: 17 additions & 3 deletions interfaces/prompting/requestprompts/requestprompts.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import (
"github.com/snapcore/snapd/interfaces/prompting"
prompting_errors "github.com/snapcore/snapd/interfaces/prompting/errors"
"github.com/snapcore/snapd/interfaces/prompting/internal/maxidmmap"
"github.com/snapcore/snapd/interfaces/prompting/patterns"
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/strutil"
Expand Down Expand Up @@ -225,7 +226,7 @@ func (pc *promptConstraints) marshalForInterface(iface string) ([]byte, error) {
switch iface {
case "home":
constraintsJSON := &promptConstraintsJSONHome{
Path: pc.path,
Path: pc.EscapedPath(),
RequestedPermissions: pc.outstandingPermissions,
AvailablePermissions: pc.availablePermissions,
}
Expand Down Expand Up @@ -274,7 +275,7 @@ func (pc *promptConstraints) equals(other *promptConstraints) bool {
// then affectedByRule is false, and no changes are made to the prompt
// constraints.
func (pc *promptConstraints) applyRuleConstraints(constraints *prompting.RuleConstraints) (affectedByRule, respond bool, deniedPermissions []string, err error) {
pathMatched, err := constraints.Match(pc.path)
pathMatched, err := constraints.Match(pc.Path())
if err != nil {
// Should not occur, only error is if path pattern is malformed,
// which would have thrown an error while parsing, not now.
Expand Down Expand Up @@ -341,11 +342,24 @@ func (pc *promptConstraints) buildResponse(deniedPermissions []string) []string
}

// Path returns the path associated with the request to which the receiving
// prompt constraints apply.
// prompt constraints apply. This is the literal path, without special path
// pattern characters escaped. This should be used when matching patterns
// against prompts.
func (pc *promptConstraints) Path() string {
return pc.path
}

// EscapedPath returns the path associated with prompt constraints, with any
// special path pattern characters escaped by a '\' character. This should be
// used in order to create a path pattern which matches the requested path.
// Thus, it should also be used when marshalling prompt constraints to send to
// a prompting client, as clients should be able to reply using the exact path
// they received in the prompt as the path pattern and have that reply apply to
// the requested path.
func (pc *promptConstraints) EscapedPath() string {
return patterns.EscapeLiteralPath(pc.path)
}

// OutstandingPermissions returns the outstanding unsatisfied permissions
// associated with the prompt.
func (pc *promptConstraints) OutstandingPermissions() []string {
Expand Down
Loading
Loading