Closed rchowinfoblox closed 3 years ago
This PR adds type-information to obligations. Once this is merged, I'll update https://github.com/infobloxopen/seal/pull/132 with support for this type-information.
$ make test demo ? github.com/infobloxopen/seal [no test files] ? github.com/infobloxopen/seal/cmd [no test files] ? github.com/infobloxopen/seal/pkg/ast [no test files] ? github.com/infobloxopen/seal/pkg/compiler [no test files] ? github.com/infobloxopen/seal/pkg/compiler/error [no test files] === RUN TestCompile rego_test.go:180: validate policy: allow subject group foo to manage petstore.pet; rego_test.go:181: rego language output generated: package foo default allow = false default deny = false base_verbs := { } allow { seal_list_contains(seal_subject.groups, `foo`) seal_list_contains(base_verbs[input.type][`manage`], input.verb) re_match(`petstore.pet`, input.type) } obligations := { } # rego functions defined by seal # Helper to get the token payload. seal_subject = payload { [header, payload, signature] := io.jwt.decode(input.jwt) } # seal_list_contains returns true if elem exists in list seal_list_contains(list, elem) { list[_] = elem } rego_test.go:180: validate policy: allow subject user foo to manage petstore.pet; rego_test.go:181: rego language output generated: package foo default allow = false default deny = false base_verbs := { } allow { seal_subject.sub == `foo` seal_list_contains(base_verbs[input.type][`manage`], input.verb) re_match(`petstore.pet`, input.type) } obligations := { } # rego functions defined by seal # Helper to get the token payload. seal_subject = payload { [header, payload, signature] := io.jwt.decode(input.jwt) } # seal_list_contains returns true if elem exists in list seal_list_contains(list, elem) { list[_] = elem } --- PASS: TestCompile (0.00s) === RUN TestCleanupSomeI --- PASS: TestCleanupSomeI (0.00s) PASS ok github.com/infobloxopen/seal/pkg/compiler/rego (cached) === RUN TestCompileCondition sql_condition_test.go:74: Test#0: success: expected error and got err=no prefix condition parse function for IDENT found sql_condition_test.go:76: Test#1: success: where=(foobar.qwerty = 'there''s a single-quote in this string') sql_condition_test.go:76: Test#2: success: where=(mysqltable.nbf < 123 AND (mysqltable.description = 'string with subject. in it')) sql_condition_test.go:76: Test#3: success: where=((NOT (mysqltable.iss = 'string with ctx. in it')) AND (mysqltable.name ~ '.*goofy.*')) sql_condition_test.go:74: Test#4: success: expected error and got err=map/array indexing not supported yet: ctx.tags["endangered"] sql_condition_test.go:74: Test#5: success: expected error and got err=IN operator not supported yet: (ctx.id in "tag-manage") --- PASS: TestCompileCondition (0.00s) PASS ok github.com/infobloxopen/seal/pkg/compiler/sql (cached) === RUN TestCompiler --- PASS: TestCompiler (0.00s) === RUN TestLanguages compiler_test.go:86: validate list of languages - supported languages: []string{"rego"} --- PASS: TestLanguages (0.00s) === RUN TestBackend === RUN TestBackend/invalid-nonwildcarded-resource-property === RUN TestBackend/invalid-nonwildcarded-resource-property-in-context === RUN TestBackend/invalid-nonwildcarded-resource-property-with-subject === RUN TestBackend/obligations-wildcard === RUN TestBackend/obligations-multi-oblig-in-single-stmt === RUN TestBackend/missing-verb-errors === RUN TestBackend/blank-subject === RUN TestBackend/context === RUN TestBackend/context-2 === RUN TestBackend/context-nested === RUN TestBackend/not-in-operator === RUN TestBackend/support-for-or-operator-simple === RUN TestBackend/missing-resource-errors === RUN TestBackend/in-operator === RUN TestBackend/invalid-resource-format-without-using-family.type-errors === RUN TestBackend/simplest-statement-with-subject === RUN TestBackend/grouping-with-parens === RUN TestBackend/grouping-with-not-and-parens === RUN TestBackend/company.personnel === RUN TestBackend/obligations-simple === RUN TestBackend/support-for-or-operator-context === RUN TestBackend/statement-with-and === RUN TestBackend/multiple-statements === RUN TestBackend/matches === RUN TestBackend/invalid-resource-not-registered === RUN TestBackend/no-swagger-actions === RUN TestBackend/obligations-context === RUN TestBackend/blank-swagger === RUN TestBackend/statement-with-not === RUN TestBackend/precedence-with-not === RUN TestBackend/tags === RUN TestBackend/obligations-multi-stmt-with-oblig === RUN TestBackend/missing-to-errors --- PASS: TestBackend (0.07s) --- PASS: TestBackend/invalid-nonwildcarded-resource-property (0.00s) --- PASS: TestBackend/invalid-nonwildcarded-resource-property-in-context (0.00s) --- PASS: TestBackend/invalid-nonwildcarded-resource-property-with-subject (0.00s) --- PASS: TestBackend/obligations-wildcard (0.00s) --- PASS: TestBackend/obligations-multi-oblig-in-single-stmt (0.00s) --- PASS: TestBackend/missing-verb-errors (0.00s) --- PASS: TestBackend/blank-subject (0.00s) --- PASS: TestBackend/context (0.00s) --- PASS: TestBackend/context-2 (0.00s) --- PASS: TestBackend/context-nested (0.00s) --- PASS: TestBackend/not-in-operator (0.00s) --- PASS: TestBackend/support-for-or-operator-simple (0.00s) --- PASS: TestBackend/missing-resource-errors (0.00s) --- PASS: TestBackend/in-operator (0.00s) --- PASS: TestBackend/invalid-resource-format-without-using-family.type-errors (0.00s) --- PASS: TestBackend/simplest-statement-with-subject (0.00s) --- PASS: TestBackend/grouping-with-parens (0.00s) --- PASS: TestBackend/grouping-with-not-and-parens (0.00s) --- PASS: TestBackend/company.personnel (0.00s) --- PASS: TestBackend/obligations-simple (0.00s) --- PASS: TestBackend/support-for-or-operator-context (0.00s) --- PASS: TestBackend/statement-with-and (0.00s) --- PASS: TestBackend/multiple-statements (0.00s) --- PASS: TestBackend/matches (0.00s) --- PASS: TestBackend/invalid-resource-not-registered (0.00s) --- PASS: TestBackend/no-swagger-actions (0.00s) --- PASS: TestBackend/obligations-context (0.00s) --- PASS: TestBackend/blank-swagger (0.00s) --- PASS: TestBackend/statement-with-not (0.00s) --- PASS: TestBackend/precedence-with-not (0.00s) --- PASS: TestBackend/tags (0.00s) --- PASS: TestBackend/obligations-multi-stmt-with-oblig (0.00s) --- PASS: TestBackend/missing-to-errors (0.00s) === RUN TestManySwaggers === RUN TestManySwaggers/global === RUN TestManySwaggers/empty === RUN TestManySwaggers/global-sw1-sw2 === RUN TestManySwaggers/global-sw2-sw1 === RUN TestManySwaggers/sw1 === RUN TestManySwaggers/sw2 --- PASS: TestManySwaggers (0.01s) --- PASS: TestManySwaggers/global (0.00s) --- PASS: TestManySwaggers/empty (0.00s) --- PASS: TestManySwaggers/global-sw1-sw2 (0.00s) --- PASS: TestManySwaggers/global-sw2-sw1 (0.00s) --- PASS: TestManySwaggers/sw1 (0.00s) --- PASS: TestManySwaggers/sw2 (0.00s) PASS ok github.com/infobloxopen/seal/pkg/compiler/test (cached) === RUN TestNextToken --- PASS: TestNextToken (0.00s) === RUN TestContextToken --- PASS: TestContextToken (0.00s) === RUN TestNextTokenComment --- PASS: TestNextTokenComment (0.00s) PASS ok github.com/infobloxopen/seal/pkg/lexer (cached) === RUN TestWhereClause === RUN TestWhereClause/simple_user === RUN TestWhereClause/simple_group === RUN TestWhereClause/simple_where_clause_compare_equal === RUN TestWhereClause/simple_where_clause_compare_not_equal === RUN TestWhereClause/simple_where_clause_compare_int === RUN TestWhereClause/simple_where_clause_compare_bool === RUN TestWhereClause/single_where_clause_and === RUN TestWhereClause/left_associative_where_clause_and === RUN TestWhereClause/where_clause_grouped_conditions === RUN TestWhereClause/where_clause_multiple_grouped_conditions --- PASS: TestWhereClause (0.00s) --- PASS: TestWhereClause/simple_user (0.00s) --- PASS: TestWhereClause/simple_group (0.00s) --- PASS: TestWhereClause/simple_where_clause_compare_equal (0.00s) --- PASS: TestWhereClause/simple_where_clause_compare_not_equal (0.00s) --- PASS: TestWhereClause/simple_where_clause_compare_int (0.00s) --- PASS: TestWhereClause/simple_where_clause_compare_bool (0.00s) --- PASS: TestWhereClause/single_where_clause_and (0.00s) --- PASS: TestWhereClause/left_associative_where_clause_and (0.00s) --- PASS: TestWhereClause/where_clause_grouped_conditions (0.00s) --- PASS: TestWhereClause/where_clause_multiple_grouped_conditions (0.00s) === RUN TestExportedParseCondition === RUN TestExportedParseCondition/no_parens === RUN TestExportedParseCondition/single_parens === RUN TestExportedParseCondition/double_parens === RUN TestExportedParseCondition/mismatched_parens condition_test.go:199: ParseCondition(`(((ctx.age > 65))`) expected error, and error returned: "expected next token to be ), got EOF instead" --- PASS: TestExportedParseCondition (0.00s) --- PASS: TestExportedParseCondition/no_parens (0.00s) --- PASS: TestExportedParseCondition/single_parens (0.00s) --- PASS: TestExportedParseCondition/double_parens (0.00s) --- PASS: TestExportedParseCondition/mismatched_parens (0.00s) === RUN TestSplitKeyValueAnnotations condition_test.go:305: Test#0: pass (remain): input=` a b c d ` remaining=` a b c d ` condition_test.go:313: Test#0: pass (annMap): input=` a b c d ` annoMap=`map[]` condition_test.go:305: Test#1: pass (remain): input=` ; a b c d ` remaining=` a b c d ` condition_test.go:313: Test#1: pass (annMap): input=` ; a b c d ` annoMap=`map[]` condition_test.go:305: Test#2: pass (remain): input=` , ; a b c d ` remaining=` a b c d ` condition_test.go:313: Test#2: pass (annMap): input=` , ; a b c d ` annoMap=`map[]` condition_test.go:305: Test#3: pass (remain): input=`k1; a b c d ` remaining=` a b c d ` condition_test.go:313: Test#3: pass (annMap): input=`k1; a b c d ` annoMap=`map[k1:]` condition_test.go:305: Test#4: pass (remain): input=`k1: v1 ; a b c d ` remaining=` a b c d ` condition_test.go:313: Test#4: pass (annMap): input=`k1: v1 ; a b c d ` annoMap=`map[k1:v1]` condition_test.go:305: Test#5: pass (remain): input=`k1: v1 , k2 ; a b c d ` remaining=` a b c d ` condition_test.go:313: Test#5: pass (annMap): input=`k1: v1 , k2 ; a b c d ` annoMap=`map[k1:v1 k2:]` condition_test.go:305: Test#6: pass (remain): input=`k1: v1 , k2 : ; a b c d ` remaining=` a b c d ` condition_test.go:313: Test#6: pass (annMap): input=`k1: v1 , k2 : ; a b c d ` annoMap=`map[k1:v1 k2:]` condition_test.go:305: Test#7: pass (remain): input=`k1: v1 , k2 : v2 ; a b c d ` remaining=` a b c d ` condition_test.go:313: Test#7: pass (annMap): input=`k1: v1 , k2 : v2 ; a b c d ` annoMap=`map[k1:v1 k2:v2]` condition_test.go:305: Test#8: pass (remain): input=`k 1: v 1 , k 2 : v 2 ; a b c d ` remaining=` a b c d ` condition_test.go:313: Test#8: pass (annMap): input=`k 1: v 1 , k 2 : v 2 ; a b c d ` annoMap=`map[k 1:v 1 k 2:v 2]` --- PASS: TestSplitKeyValueAnnotations (0.00s) === RUN TestLetStatements --- PASS: TestLetStatements (0.00s) PASS ok github.com/infobloxopen/seal/pkg/parser (cached) === RUN TestLookupOperator === RUN TestLookupOperator/invalid_empty === RUN TestLookupOperator/equal_to === RUN TestLookupOperator/not_equal === RUN TestLookupOperator/less_than === RUN TestLookupOperator/greater_than === RUN TestLookupOperator/less_than_or_equal_to === RUN TestLookupOperator/greater_than_or_equal_to === RUN TestLookupOperator/not === RUN TestLookupOperator/and === RUN TestLookupOperator/or === RUN TestLookupOperator/matches --- PASS: TestLookupOperator (0.00s) --- PASS: TestLookupOperator/invalid_empty (0.00s) --- PASS: TestLookupOperator/equal_to (0.00s) --- PASS: TestLookupOperator/not_equal (0.00s) --- PASS: TestLookupOperator/less_than (0.00s) --- PASS: TestLookupOperator/greater_than (0.00s) --- PASS: TestLookupOperator/less_than_or_equal_to (0.00s) --- PASS: TestLookupOperator/greater_than_or_equal_to (0.00s) --- PASS: TestLookupOperator/not (0.00s) --- PASS: TestLookupOperator/and (0.00s) --- PASS: TestLookupOperator/or (0.00s) --- PASS: TestLookupOperator/matches (0.00s) PASS ok github.com/infobloxopen/seal/pkg/token (cached) === RUN TestIsNilInterface --- PASS: TestIsNilInterface (0.00s) === RUN TestNewTypeFromOpenAPIv3 types_test.go:17: got type: petstore.pet types_test.go:19: got action: &types.swaggerAction{name:"allow", schema:(*openapi3.SchemaRef)(0xc000134540)} types_test.go:23: TODO: get type schema for action types_test.go:19: got action: &types.swaggerAction{name:"deny", schema:(*openapi3.SchemaRef)(nil)} types_test.go:23: TODO: get type schema for action --- PASS: TestNewTypeFromOpenAPIv3 (0.00s) PASS ok github.com/infobloxopen/seal/pkg/types (cached) ./seal compile \ -s docs/source/examples/petstore/petstore.jwt.swagger \ -s docs/source/examples/petstore/petstore.tags.swagger \ -s docs/source/examples/petstore/petstore.all.swagger \ -f docs/source/examples/petstore/petstore.all.seal \ > docs/source/examples/petstore/petstore.all.rego.compiled INFO[0000] set logging format logging.format=text INFO[0000] logging level logging.level=info cat docs/source/examples/petstore/petstore.all.rego.compiled package petstore.all default allow = false default deny = false base_verbs := { "petstore.order": { "approve": [ "approve", ], "deliver": [ "deliver", ], "inspect": [ "list", "watch", ], "manage": [ "create", "delete", "update", "get", "list", "watch", ], "read": [ "get", "list", "watch", ], "ship": [ "ship", ], "use": [ "update", "get", "list", "watch", ], }, "petstore.pet": { "buy": [ "buy", ], "inspect": [ "list", "watch", ], "manage": [ "create", "delete", "update", "get", "list", "watch", ], "provision": [ "provision", ], "read": [ "get", "list", "watch", ], "sell": [ "sell", ], "use": [ "update", "get", "list", "watch", ], }, "petstore.user": { "inspect": [ "list", "watch", ], "manage": [ "create", "delete", "update", "get", "list", "watch", ], "read": [ "get", "list", "watch", ], "sign_in": [ "sign_in", ], "use": [ "update", "get", "list", "watch", ], }, } deny { seal_list_contains(base_verbs[input.type][`deliver`], input.verb) re_match(`petstore.order`, input.type) seal_list_contains(seal_subject.groups, `boss`) } deny { seal_list_contains(base_verbs[input.type][`use`], input.verb) re_match(`petstore.order`, input.type) some i input.ctx[i]["id"] == "-1" } deny { seal_list_contains(base_verbs[input.type][`use`], input.verb) re_match(`petstore.user`, input.type) some i input.ctx[i]["id"] == "-1" } deny { seal_list_contains(base_verbs[input.type][`use`], input.verb) re_match(`petstore.order`, input.type) seal_subject.iss != "context.petstore.swagger.io" } deny { seal_list_contains(base_verbs[input.type][`use`], input.verb) re_match(`petstore.user`, input.type) seal_subject.iss != "context.petstore.swagger.io" } deny { seal_list_contains(base_verbs[input.type][`deliver`], input.verb) re_match(`petstore.order`, input.type) some i input.ctx[i]["status"] == "delivered" } deny { seal_list_contains(seal_subject.groups, `regexp`) seal_list_contains(base_verbs[input.type][`use`], input.verb) re_match(`petstore.*`, input.type) re_match(`@petstore.swagger.io$`, seal_subject.jti) } deny { seal_list_contains(seal_subject.groups, `everyone`) seal_list_contains(base_verbs[input.type][`use`], input.verb) re_match(`petstore.*`, input.type) seal_subject.iss != "petstore.swagger.io" } deny { seal_list_contains(seal_subject.groups, `everyone`) seal_list_contains(base_verbs[input.type][`buy`], input.verb) re_match(`petstore.pet`, input.type) some i input.ctx[i]["age"] <= 2 input.ctx[i]["name"] == "specificPetName" } deny { seal_list_contains(seal_subject.groups, `banned`) seal_list_contains(base_verbs[input.type][`manage`], input.verb) re_match(`petstore.*`, input.type) } deny { seal_list_contains(seal_subject.groups, `managers`) seal_list_contains(base_verbs[input.type][`sell`], input.verb) re_match(`petstore.pet`, input.type) some i input.ctx[i]["status"] != "available" } deny { seal_list_contains(seal_subject.groups, `fussy`) seal_list_contains(base_verbs[input.type][`buy`], input.verb) re_match(`petstore.pet`, input.type) not line13_not1_cnd not line13_not2_cnd } allow { seal_list_contains(seal_subject.groups, `fussy`) seal_list_contains(base_verbs[input.type][`buy`], input.verb) re_match(`petstore.pet`, input.type) not line14_not1_cnd } allow { seal_list_contains(seal_subject.groups, `not_operator_precedence`) seal_list_contains(base_verbs[input.type][`buy`], input.verb) re_match(`petstore.pet`, input.type) some i not line15_not1_cnd input.ctx[i]["potty_trained"] } deny { seal_list_contains(seal_subject.groups, `everyone`) seal_list_contains(base_verbs[input.type][`buy`], input.verb) re_match(`petstore.pet`, input.type) some i input.ctx[i]["tags"]["endangered"] == "true" } allow { seal_list_contains(seal_subject.groups, `operators`) seal_list_contains(base_verbs[input.type][`use`], input.verb) re_match(`petstore.*`, input.type) } allow { seal_list_contains(seal_subject.groups, `managers`) seal_list_contains(base_verbs[input.type][`manage`], input.verb) re_match(`petstore.*`, input.type) } allow { seal_subject.sub == `cto@petstore.swagger.io` seal_list_contains(base_verbs[input.type][`manage`], input.verb) re_match(`petstore.*`, input.type) } allow { seal_list_contains(base_verbs[input.type][`inspect`], input.verb) re_match(`petstore.pet`, input.type) } allow { seal_list_contains(seal_subject.groups, `everyone`) seal_list_contains(base_verbs[input.type][`inspect`], input.verb) re_match(`petstore.pet`, input.type) } allow { seal_list_contains(seal_subject.groups, `customers`) seal_list_contains(base_verbs[input.type][`read`], input.verb) re_match(`petstore.pet`, input.type) } allow { seal_list_contains(seal_subject.groups, `customers`) seal_list_contains(base_verbs[input.type][`buy`], input.verb) re_match(`petstore.pet`, input.type) some i input.ctx[i]["status"] == "available" } allow { seal_list_contains(seal_subject.groups, `breeders_maltese`) seal_list_contains(base_verbs[input.type][`buy`], input.verb) re_match(`petstore.pet`, input.type) some i input.ctx[i]["status"] == "reserved" input.ctx[i]["breed"] == "maltese" } allow { seal_list_contains(seal_subject.groups, `employees`) seal_list_contains(base_verbs[input.type][`inspect`], input.verb) re_match(`petstore.order`, input.type) some i input.ctx[i]["status"] == "delivered" } allow { seal_list_contains(seal_subject.groups, `supervisors`) seal_list_contains(base_verbs[input.type][`manage`], input.verb) re_match(`petstore.user`, input.type) some i re_match(`.*@acme.com`, input.ctx[i]["email"]) } line13_not1_cnd { some i input.ctx[i]["neutered"] } line13_not2_cnd { some i input.ctx[i]["potty_trained"] } line14_not1_cnd { some i input.ctx[i]["neutered"] input.ctx[i]["potty_trained"] } line15_not1_cnd { some i input.ctx[i]["neutered"] } obligations := { `stmt20`: [ `type:petstore.order; (ctx.marketplace != "amazon")`, ], `stmt21`: [ `type:petstore.user; ((ctx.occupation != "unemployed") and (ctx.salary > 200000))`, ], } # rego functions defined by seal # Helper to get the token payload. seal_subject = payload { [header, payload, signature] := io.jwt.decode(input.jwt) } # seal_list_contains returns true if elem exists in list seal_list_contains(list, elem) { list[_] = elem } cp docs/source/examples/petstore/petstore.all.rego.compiled docs/source/examples/petstore/petstore.all.rego # beware that check-rego.sh reformats the compiled rego files... ./docs/source/examples/check-rego.sh docs/source/examples/petstore + IMAGE=openpolicyagent/opa:latest + [[ -z docs/source/examples/petstore ]] + [[ ! -d docs/source/examples/petstore ]] ++ cd docs/source/examples/petstore ++ /bin/pwd + TOP=/home/riccho/go/src/github.com/infobloxopen/seal/docs/source/examples/petstore + cd /home/riccho/go/src/github.com/infobloxopen/seal/docs/source/examples/petstore + docker run -v /home/riccho/go/src/github.com/infobloxopen/seal/docs/source/examples/petstore:/data -w /data openpolicyagent/opa:latest fmt -w . + case "$(basename $0)" in ++ basename ./docs/source/examples/check-rego.sh + docker run --rm -v /home/riccho/go/src/github.com/infobloxopen/seal/docs/source/examples/petstore:/data -w /data openpolicyagent/opa:latest test -v petstore.all.rego petstore.all.test_bench.rego petstore.all.test.rego petstore.all.mock.json data.petstore.all.test_in: PASS (1.413064ms) data.petstore.all.test_in_negative: PASS (910.83µs) data.petstore.all.test_not_operator_precedence_positive: PASS (783.722µs) data.petstore.all.test_not_operator_precedence_negative1: PASS (745.253µs) data.petstore.all.test_not_operator_precedence_negative2: PASS (740.966µs) data.petstore.all.test_not_operator_precedence_negative3: PASS (939.431µs) data.petstore.all.test_regexp: PASS (1.942577ms) data.petstore.all.test_regexp_negative: PASS (878.979µs) data.petstore.all.test_ctx_usage_multiply: PASS (1.450903ms) data.petstore.all.test_ctx_usage_negative_multi_ctx: PASS (1.307227ms) data.petstore.all.test_ctx_usage_negative: PASS (1.318162ms) data.petstore.all.test_use_tags: PASS (1.298935ms) data.petstore.all.test_use_tags_negative: PASS (1.271042ms) data.petstore.all.test_use_tags_negative_missing_endangered_tag: PASS (2.244158ms) data.petstore.all.test_use_petstore_jwt: PASS (1.030092ms) data.petstore.all.test_use_petstore_jwt_negative: PASS (884.392µs) data.petstore.all.test_banned_deny: PASS (933.886µs) data.petstore.all.test_banned_deny_negative: PASS (827.555µs) data.petstore.all.test_inspect: PASS (751.168µs) data.petstore.all.test_inspect_negative: PASS (632.022µs) data.petstore.all.test_read: PASS (1.55778ms) data.petstore.all.test_read_negative: PASS (1.404262ms) data.petstore.all.test_manage_cto: PASS (1.301626ms) data.petstore.all.test_blank_subject: PASS (1.467127ms) data.petstore.all.test_bench_deny_in_map_species: PASS (272.052µs) data.petstore.all.test_bench_deny_in_map_species_2nd: PASS (264.26µs) data.petstore.all.test_bench_deny_in_map_species_negative: PASS (332.596µs) data.petstore.all.test_bench_deny_in_map_species_negative_2nd: PASS (244.519µs) -------------------------------------------------------------------------------- PASS: 28/28 + git diff --exit-code petstore.all.rego petstore.all.test_bench.rego petstore.all.test.rego git diff --exit-code docs/source/examples/petstore ### petstore example passed REGO OPA tests
This PR adds type-information to obligations. Once this is merged, I'll update https://github.com/infobloxopen/seal/pull/132 with support for this type-information.