Skip to content

Commit

Permalink
Allow slash in policy id/path
Browse files Browse the repository at this point in the history
As part of this change, the /policies/{id}/raw API has been removed in
favour of a "source" query parameter. When GET
/policies/{id}?source=true is received, the server returns the
raw/source of the policy.

Also, cleaned up handling of boolean query params.

Fixes #292
  • Loading branch information
tsandall committed Mar 11, 2017
1 parent a429127 commit 6d55a28
Show file tree
Hide file tree
Showing 10 changed files with 90 additions and 81 deletions.
81 changes: 39 additions & 42 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import (

"crypto/tls"

"net/url"

"github.com/gorilla/mux"
"github.com/open-policy-agent/opa/ast"
"github.com/open-policy-agent/opa/server/authorizer"
Expand Down Expand Up @@ -82,10 +84,9 @@ func New() *Server {
s.registerHandlerV1(router, "/data/{path:.+}", "POST", s.v1DataPost)
s.registerHandlerV1(router, "/data", "POST", s.v1DataPost)
s.registerHandlerV1(router, "/policies", "GET", s.v1PoliciesList)
s.registerHandlerV1(router, "/policies/{id}", "DELETE", s.v1PoliciesDelete)
s.registerHandlerV1(router, "/policies/{id}", "GET", s.v1PoliciesGet)
s.registerHandlerV1(router, "/policies/{id}/raw", "GET", s.v1PoliciesRawGet)
s.registerHandlerV1(router, "/policies/{id}", "PUT", s.v1PoliciesPut)
s.registerHandlerV1(router, "/policies/{path:.+}", "DELETE", s.v1PoliciesDelete)
s.registerHandlerV1(router, "/policies/{path:.+}", "GET", s.v1PoliciesGet)
s.registerHandlerV1(router, "/policies/{path:.+}", "PUT", s.v1PoliciesPut)
s.registerHandlerV1(router, "/query", "GET", s.v1QueryGet)
router.HandleFunc("/", s.indexGet).Methods("GET")
s.Handler = router
Expand Down Expand Up @@ -297,7 +298,7 @@ func (s *Server) v1DataGet(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vars := mux.Vars(r)
path := stringPathToDataRef(vars["path"])
pretty := getPretty(r.URL.Query()["pretty"])
pretty := getBoolParam(r.URL, types.ParamPrettyV1, true)
explainMode := getExplain(r.URL.Query()["explain"])
input, nonGround, err := parseInput(r.URL.Query()[types.ParamInputV1])

Expand Down Expand Up @@ -410,7 +411,7 @@ func (s *Server) v1DataPost(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vars := mux.Vars(r)
path := stringPathToDataRef(vars["path"])
pretty := getPretty(r.URL.Query()["pretty"])
pretty := getBoolParam(r.URL, types.ParamPrettyV1, true)
explainMode := getExplain(r.URL.Query()["explain"])

input, err := readInput(r.Body)
Expand Down Expand Up @@ -524,7 +525,7 @@ func (s *Server) v1DataPut(w http.ResponseWriter, r *http.Request) {
func (s *Server) v1PoliciesDelete(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vars := mux.Vars(r)
id := vars["id"]
id := vars["path"]

txn, err := s.store.NewTransaction(ctx)
if err != nil {
Expand Down Expand Up @@ -563,7 +564,8 @@ func (s *Server) v1PoliciesDelete(w http.ResponseWriter, r *http.Request) {
func (s *Server) v1PoliciesGet(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vars := mux.Vars(r)
id := vars["id"]
path := vars["path"]
source := getBoolParam(r.URL, types.ParamSourceV1, true)

txn, err := s.store.NewTransaction(ctx)
if err != nil {
Expand All @@ -573,47 +575,29 @@ func (s *Server) v1PoliciesGet(w http.ResponseWriter, r *http.Request) {

defer s.store.Close(ctx, txn)

_, _, err = s.store.GetPolicy(txn, id)
_, bs, err := s.store.GetPolicy(txn, path)
if err != nil {
writer.ErrorAuto(w, err)
return
}

if source {
writer.Bytes(w, 200, bs)
return
}

c := s.Compiler()

response := types.PolicyGetResponseV1{
Result: types.PolicyV1{
ID: id,
Module: c.Modules[id],
ID: path,
Module: c.Modules[path],
},
}

writer.JSON(w, 200, response, true)
}

func (s *Server) v1PoliciesRawGet(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vars := mux.Vars(r)
id := vars["id"]

txn, err := s.store.NewTransaction(ctx)
if err != nil {
writer.ErrorAuto(w, err)
return
}

defer s.store.Close(ctx, txn)

_, bs, err := s.store.GetPolicy(txn, id)

if err != nil {
writer.ErrorAuto(w, err)
return
}

writer.Bytes(w, 200, bs)
}

func (s *Server) v1PoliciesList(w http.ResponseWriter, r *http.Request) {

policies := []types.PolicyV1{}
Expand All @@ -638,15 +622,15 @@ func (s *Server) v1PoliciesList(w http.ResponseWriter, r *http.Request) {
func (s *Server) v1PoliciesPut(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vars := mux.Vars(r)
id := vars["id"]
path := vars["path"]

buf, err := ioutil.ReadAll(r.Body)
if err != nil {
writer.ErrorString(w, http.StatusBadRequest, types.CodeInvalidParameter, err)
return
}

parsedMod, err := ast.ParseModule(id, string(buf))
parsedMod, err := ast.ParseModule(path, string(buf))

if err != nil {
switch err := err.(type) {
Expand All @@ -673,7 +657,7 @@ func (s *Server) v1PoliciesPut(w http.ResponseWriter, r *http.Request) {
defer s.store.Close(ctx, txn)

mods := s.store.ListPolicies(txn)
mods[id] = parsedMod
mods[path] = parsedMod

c := ast.NewCompiler()

Expand All @@ -682,7 +666,7 @@ func (s *Server) v1PoliciesPut(w http.ResponseWriter, r *http.Request) {
return
}

if err := s.store.InsertPolicy(txn, id, parsedMod, buf, s.persist); err != nil {
if err := s.store.InsertPolicy(txn, path, parsedMod, buf, s.persist); err != nil {
writer.ErrorAuto(w, err)
return
}
Expand All @@ -691,8 +675,8 @@ func (s *Server) v1PoliciesPut(w http.ResponseWriter, r *http.Request) {

response := types.PolicyPutResponseV1{
Result: types.PolicyV1{
ID: id,
Module: c.Modules[id],
ID: path,
Module: c.Modules[path],
},
}

Expand All @@ -702,7 +686,7 @@ func (s *Server) v1PoliciesPut(w http.ResponseWriter, r *http.Request) {
func (s *Server) v1QueryGet(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
values := r.URL.Query()
pretty := getPretty(r.URL.Query()["pretty"])
pretty := getBoolParam(r.URL, types.ParamPrettyV1, true)
explainMode := getExplain(r.URL.Query()["explain"])
qStrs := values["q"]
if len(qStrs) == 0 {
Expand Down Expand Up @@ -874,12 +858,25 @@ func stringPathToRef(s string) (r ast.Ref) {
return r
}

func getPretty(p []string) bool {
func getBoolParam(url *url.URL, name string, ifEmpty bool) bool {

p, ok := url.Query()[name]
if !ok {
return false
}

// Query params w/o values are represented as slice (of len 1) with an
// empty string.
if len(p) == 1 && p[0] == "" {
return ifEmpty
}

for _, x := range p {
if strings.ToLower(x) == "true" {
return true
}
}

return false
}

Expand Down
14 changes: 12 additions & 2 deletions server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -722,7 +722,7 @@ func TestPoliciesGetV1(t *testing.T) {
}
}

func TestPoliciesGetRawV1(t *testing.T) {
func TestPoliciesGetSourceV1(t *testing.T) {
f := newFixture(t)
put := newReqV1("PUT", "/policies/1", testMod)
f.server.Handler.ServeHTTP(f.recorder, put)
Expand All @@ -732,7 +732,7 @@ func TestPoliciesGetRawV1(t *testing.T) {
}

f.reset()
get := newReqV1("GET", "/policies/1/raw", "")
get := newReqV1("GET", "/policies/1?source", "")

f.server.Handler.ServeHTTP(f.recorder, get)

Expand Down Expand Up @@ -773,6 +773,16 @@ func TestPoliciesDeleteV1(t *testing.T) {
}
}

func TestPoliciesPathSlashes(t *testing.T) {
f := newFixture(t)
if err := f.v1("PUT", "/policies/a/b/c.rego", testMod, 200, ""); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if err := f.v1("GET", "/policies/a/b/c.rego", testMod, 200, ""); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
}

func TestQueryV1(t *testing.T) {
f := newFixture(t)
get := newReqV1("GET", `/query?q=a=[1,2,3]%3Ba[i]=x`, "")
Expand Down
8 changes: 8 additions & 0 deletions server/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,14 @@ const (
// ParamInputV1 defines the name of the HTTP URL parameter that specifies
// values for the "input" document.
ParamInputV1 = "input"

// ParamSourceV1 defines the name of the HTTP URL parameter that indicates
// the client wants to receive the raw (uncompiled) version of the module.
ParamSourceV1 = "source"

// ParamPrettyV1 defines the name of the HTTP URL parameter that indicates
// the client wants to receive a pretty-printed version of the response.
ParamPrettyV1 = "pretty"
)

// WriteConflictErr represents an error condition raised if the caller attempts
Expand Down
9 changes: 5 additions & 4 deletions site/_scripts/rest-examples/example1.rego
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import data.servers
import data.networks
import data.ports

public_servers[server] :-
server = servers[_],
server.ports[_] = ports[k].id,
ports[k].networks[_] = networks[m].id,
public_servers[server] {
server = servers[_]
server.ports[_] = ports[k].id
ports[k].networks[_] = networks[m].id
networks[m].public = true
}
7 changes: 4 additions & 3 deletions site/_scripts/rest-examples/example2.rego
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ package opa.examples

import data.servers

violations[server] :-
server = servers[_],
server.protocols[_] = "http",
violations[server] {
server = servers[_]
server.protocols[_] = "http"
public_servers[server]
}
2 changes: 1 addition & 1 deletion site/_scripts/rest-examples/example3.rego
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ package opa.examples

import input.example.flag

allow_request :- flag = true
allow_request { flag = true }
6 changes: 4 additions & 2 deletions site/_scripts/rest-examples/example4.rego
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package opa.examples

import input.container

allow_container :-
allow_container {
not seccomp_unconfined
}

seccomp_unconfined :-
seccomp_unconfined {
container.HostConfig.SecurityOpt[_] = "seccomp:unconfined"
}
8 changes: 4 additions & 4 deletions site/_scripts/rest-examples/gen-examples.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ function main() {
# execute each of the examples from the reference
list_policies
get_a_policy
get_a_raw_policy
get_a_policy_source
create_or_update_a_policy
delete_a_policy
get_a_document
Expand All @@ -40,10 +40,10 @@ function get_a_policy() {
echo ""
}

function get_a_raw_policy() {
echo "### Get a Raw Policy"
function get_a_policy_source() {
echo "#### Example Request For Source"
echo ""
curl $BASE_URL/policies/example1/raw -s -v
curl $BASE_URL/policies/example1?source=true -s -v
echo ""
}

Expand Down
9 changes: 5 additions & 4 deletions site/documentation/references/deployments/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,10 @@ curl -i localhost:8181/
#### Logging

OPA focuses on providing debugging support through well-defined APIs and
whenever possible, OPA returns detailed, structured error messages to clients.
By default, OPA only emits log messages in response to critical internal events.
When OPA is healthy, the log stream is quiet.
detailed error messages in response to client requests.

Log verbosity can be increased to provide debugging information about API
requests and responses.

The `--v=N` flag controls log verbosity:

Expand All @@ -78,7 +79,7 @@ The `--v=N` flag controls log verbosity:
With response logging enabled, API response metadata is logged:

```
I0308 20:20:16.107720 1 logging.go:43] 172.17.0.1:57000 GET /v1/policies 200 18 0.821549ms
I0311 18:54:17.110689 1 logging.go:60] rid=1: 172.17.0.1:60944 PUT /v1/policies/test 200 3683 4.601334ms
```

These log messages include:
Expand Down
27 changes: 8 additions & 19 deletions site/documentation/references/rest/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -597,29 +597,13 @@ Content-Type: application/json
}
```

#### Status Codes

- **200** - no error
- **404** - not found
- **500** - server error

### Get a Raw Policy

```
GET /v1/policies/<id>/raw
```

Get a raw policy module.

Returns the raw policy module content that was sent by the client when the policy was created or last updated.

#### Example Request
#### Example Request For Source

```http
GET /v1/policies/example1/raw HTTP/1.1
GET /v1/policies/example1?source=true HTTP/1.1
```

#### Example Response
#### Example Response For Source

```http
HTTP/1.1 200 OK
Expand All @@ -641,6 +625,11 @@ public_servers[server] {
}
```

#### Query Parameters

- **source** - If `true` the response will contain the raw source instead of
the AST.

#### Status Codes

- **200** - no error
Expand Down

0 comments on commit 6d55a28

Please sign in to comment.