Repository navigation
PermissionController 2.0 #28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
147 commits
Select commit
Hold shift + click to select a range
bc86edc
Begin work on PermissionsController refactor
rekmarks 848f4c5
Update function generators, add caveat types test
rekmarks 6ed5d49
Fixup caveat function gen. types and errors
rekmarks 746f1bf
Use modifed ZCAP-LD interfaces for caveats and permissions
rekmarks d678853
Add errors, some basic functions
rekmarks ff38cce
Refactor caveats as middleware functions
rekmarks 87d3a04
Implement state management primitives
rekmarks cb86f11
chore: lint, post-rebase fixup
rekmarks fa793c3
domain -> actor, update actions/events, register action handlers
rekmarks 8a2f313
Add getCaveatValue static method
rekmarks 724f8e1
Fixups: return types, comments, code clarity, etc.
rekmarks bd46ea5
Clean up events and actions
rekmarks dc19143
actors -> subjects
rekmarks 718e8b7
Clean up caveat processing
rekmarks fc544c5
Add PermissionEnforcer
rekmarks 3f7532a
Add consumer-specified caveat value validation
rekmarks 741cb76
Add revokePermissionForAllSubjects
rekmarks 8ea589f
Rename some stuff in the PermissionEnforcer
rekmarks e59568d
Post-rebase fixup
rekmarks 7e1ea6d
DRY up some restricted method validation
rekmarks 360fc34
Remove safelyExecuteRestrictedMethod
rekmarks f1b65bb
Re-implement caveat decoration per suggestion by @Gudahtt
rekmarks 6c66ab6
Require that caveat functions are async
rekmarks 4088421
Update caveat state management
rekmarks 2024b51
Fixup / tweak
rekmarks 747f120
Implement RPC methods, use types over interfaces
rekmarks 3849fc1
Fix requestPermissions return value
rekmarks 7e00e61
Add permission specifications and accept/reject hooks
rekmarks 48289f7
Remove some cruft
rekmarks b517440
Add enums, export controller, refactor internal exports
rekmarks 5fef679
Rename SubjectMetadata to PermissionsSubjectMetadata
rekmarks 44ef0b6
fixup! Rename SubjectMetadata to PermissionsSubjectMetadata
rekmarks 047e787
Assume that permission factories produce valid outputs
rekmarks 2214b7c
Various tweaks and fixups
rekmarks 19219b3
@metamask/controllers@15.0.0
rekmarks b262c88
Make PermissionEnforcer a PermissionController property
rekmarks 14474a8
WIP: Tests
rekmarks 4b4efbe
Remove temporary type declaration files
rekmarks a761273
Add permission system readme, rename some things
rekmarks db1fd30
Post-rebase fixup
rekmarks fc99e7d
Stop using 'in' operator
rekmarks 8a07f64
WIP: Improved type safety 1 (unstable)
rekmarks fd98a4a
WIP: Improved type safety 2 (builds)
rekmarks d8cbb09
WIP: Improved type safety 3 (mostly cleanup left)
rekmarks 27a7a36
Post-rebase fixup
rekmarks 95e4580
WIP: Improved type safety 4 (fix permission specifications)
rekmarks 5ab713f
Complete: Improved type safety
rekmarks 4c15b7a
Unit tests: PermissionController.grantPermissions
rekmarks c912a4a
Fix caveat function type safety for namespaced permissions
rekmarks 7cfd26b
Some type fixup
rekmarks c51f6d9
Add HasPermissions action
rekmarks d3b1984
Fix some parameters, rename an export
rekmarks e1a6242
Add back getCaveat, refactor permission revocation
rekmarks 2deff4f
Unit tests: PermissionController, continued
rekmarks 6d2e92b
Update permission requestData handling
rekmarks 8860625
Unit tests: PermissionController, revocation complete
rekmarks 0d4f5b4
Unit tests: PermissionController, caveats complete
rekmarks 9fd6d67
Unit tests: PermissionController, caveats complete
rekmarks f7b2798
Unit tests: PermissionController, caveat errors
rekmarks ad34223
Fix target key validation
rekmarks 9aeb9f2
Unit tests: PermissionController, actions
rekmarks fdda927
Remove some unused errors
rekmarks a22697e
fixup! Remove some unused errors
rekmarks a4fa33d
Add test helper docstrings, more test cases
rekmarks 8326953
Add additional runtime validation to grantPermissions
rekmarks 74e2fb7
Consolidate enforcement in PermissionController
rekmarks a8acab4
Fix restricted method params type
rekmarks 0e216de
Unit tests: PermissionController.requestPermissions
rekmarks 4a7f73f
Document requestPermissions and related methods
rekmarks 04a8352
Unit tests: accept and rejectPermissionsRequest
rekmarks b9fbe47
Unit tests: begin executeRestrictedMethod
rekmarks 4b74e21
WIP: Restricted method types
rekmarks 3acb6f3
WIP: Restricted method types II
rekmarks 531d5ad
WIP: Messy
rekmarks 6e151ae
WIP: Refactor controller types (Builds)
rekmarks f5cd9d0
WIP: Refactor controller types II (Builds)
rekmarks eeb6812
WIP: Refactor controller types III (caveat specifications generic)
rekmarks fa61f4f
WIP: Refactor controller types IV (cleanup)
rekmarks cf0f547
WIP: Refactor controller types V (more cleanup)
rekmarks 8383525
Refactor controller types 😭🙏
rekmarks d27dcee
Restore caveat method type safety
rekmarks b56b354
fixup! Restore caveat method type safety
rekmarks 4078aee
Status quo ante type refactor
rekmarks 99c8454
Unit tests: PermissionController, complete
rekmarks 4df1d36
Unit tests: Complete Caveat, Permission, and errors files
rekmarks 8977b19
Some comment / type fixup
rekmarks 9b15377
Add PermissionController.updateAllCaveatsOfType
rekmarks 8ef3113
Fix permission specification getting
rekmarks 4c410be
Unit tests: permission middleware
rekmarks d80c31c
Unit tests: fin.
rekmarks f171a35
Update README
rekmarks 0fed096
Minor test wording fixup
rekmarks c492759
Update packages/controllers/src/permissions/README.md
rekmarks 33105e1
Update packages/controllers/src/permissions/README.md
rekmarks d724b56
Update packages/controllers/src/permissions/README.md
rekmarks 0556f82
Simplify types
rekmarks 6fed1c8
fixup! Simplify types
rekmarks ea899da
More documentation and type fixup
rekmarks 4a1dd83
Fix parameter names of ExtractArrayMembers
rekmarks 1d90813
Fix the ExtractPermission docstring, take II
rekmarks 8fa50a5
Fix readme
rekmarks b1105f9
Replace GenericFunction with something more specific
rekmarks c3cbe8d
Remove GenericPermission, use PermissionConstraint
rekmarks fa86d30
Fixup
rekmarks b14367f
More type fixup
rekmarks 8603b41
Rename more types from Validate* to Valid*
rekmarks 9204cfc
Get rid of PermissionValidator generic
rekmarks 3d3cdf2
Improve caveat specification type safety
rekmarks 20298a6
Remove leftover comment
rekmarks 3629734
Replace state metadata and default state consts with getters
rekmarks fca8ab5
Simplify PermissionControllerOptions
rekmarks a06545e
Improve PermissionControllerSubjects
rekmarks d42c119
Update packages/controllers/src/permissions/PermissionController.ts
rekmarks b0b7997
Address review feedback
rekmarks f2c8e49
Simplify permission-middleware types, add docs
rekmarks 799f9fa
Verify that caveat values are JSON-compatible
rekmarks 226ecaa
Apply suggestions from code review
rekmarks 2aed963
fixup! Apply suggestions from code review
rekmarks 37b75f9
Remove leftover typecast comments
rekmarks 4d24c94
Rename Permission generic type to ValidPermission
rekmarks 5ce5f80
Add NonEmptyArray type guard
rekmarks 9307b5a
Apply suggestions from code review
rekmarks b8fbba7
fixup! Apply suggestions from code review
rekmarks 11e318e
Improve some validation, address review feedback
rekmarks 606ed4f
Apply suggestions from code review
rekmarks e1e50dd
Clean up some target name and key parameter types
rekmarks fb667ff
Rationalize getRestrictedMethod implementations
rekmarks 47ff014
Fix a comment
rekmarks de43571
fixup! Fix a comment
rekmarks 91cf3df
Address review feedback
rekmarks 0655728
Apply suggestions from code review
rekmarks 2bb2c55
fixup! Apply suggestions from code review
rekmarks c2fd150
Remove constructCaveat
rekmarks 36efcf7
Improve requested permission / caveat validation
rekmarks 28a6365
Make wallet_requestPermissions more readable
rekmarks 04bcbc4
Improve variable name in wallet_getPermissions implementation
rekmarks 72524c3
Improve KeyOfTargetName docstring
rekmarks ff0b6eb
Simplify ExtractArrayMembers
rekmarks bc7fdc3
Make ExtractArrayMembers resolve to never for the empty tuple
rekmarks 9299cae
Simplify ExtractAllowedCaveatTypes
rekmarks 2f67293
Remove unnecessary constructPermission cast
rekmarks 1ccbaf7
Rename GenericTargetName to TargetName
rekmarks 56ca078
Encapsulate ValidTargetName inside ExtractPermissionTargetNames
rekmarks 14ec184
Simply CaveatSpecificationMap type
rekmarks 3014a63
Add a TODO comment
rekmarks 118bbc9
Remove unused default branches
rekmarks b4fae68
Change requestPermission origin parameter to subject metadata
rekmarks File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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
This file contains hidden or 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
This file contains hidden or 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 |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| export * from './services/WebWorkerExecutionEnvironmentService'; | ||
| export * from './plugins/PluginController'; | ||
| export * from './resource/ExternalResourceController'; | ||
| export * from './services/ExecutionEnvironmentService'; | ||
| export * from './permissions'; | ||
| export * from './plugins'; | ||
| export * from './resource'; | ||
| export * from './services'; |
This file contains hidden or 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,60 @@ | ||
| import * as errors from './errors'; | ||
| import { | ||
| CaveatSpecificationConstraint, | ||
| CaveatSpecificationMap, | ||
| decorateWithCaveats, | ||
| PermissionConstraint, | ||
| } from '.'; | ||
|
|
||
| // This function is mostly tested through the PermissionController tests, | ||
| // we just add a couple here for completeness. | ||
| describe('decorateWithCaveats', () => { | ||
| it('decorates a method with caveat', async () => { | ||
| const methodImplementation = () => [1, 2, 3]; | ||
|
|
||
| const caveatSpecifications = { | ||
| reverse: { | ||
| decorator: (method: any, _caveat: any) => async () => { | ||
| return (await method()).reverse(); | ||
| }, | ||
| }, | ||
| } as unknown as CaveatSpecificationMap<CaveatSpecificationConstraint>; | ||
|
|
||
| const permission = { | ||
| caveats: [{ type: 'reverse', value: null }], | ||
| } as unknown as PermissionConstraint; | ||
|
|
||
| const decorated = decorateWithCaveats( | ||
| methodImplementation, | ||
| permission, | ||
| caveatSpecifications, | ||
| ); | ||
|
|
||
| expect(methodImplementation()).toStrictEqual([1, 2, 3]); | ||
| expect(await decorated({} as any)).toStrictEqual([3, 2, 1]); | ||
| }); | ||
|
|
||
| it('throws an error if the caveat type is unrecognized', () => { | ||
| const methodImplementation = () => [1, 2, 3]; | ||
|
|
||
| const caveatSpecifications = { | ||
| reverse: { | ||
| decorator: (method: any, _caveat: any) => () => | ||
| method().reverse() as any, | ||
| }, | ||
| } as unknown as CaveatSpecificationMap<CaveatSpecificationConstraint>; | ||
|
|
||
| const permission = { | ||
| // This type doesn't exist | ||
| caveats: [{ type: 'kaplar', value: null }], | ||
| } as unknown as PermissionConstraint; | ||
|
|
||
| expect(() => | ||
| decorateWithCaveats( | ||
| methodImplementation, | ||
| permission, | ||
| caveatSpecifications, | ||
| )({} as any), | ||
| ).toThrow(new errors.UnrecognizedCaveatTypeError('kaplar')); | ||
| }); | ||
| }); |
This file contains hidden or 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,227 @@ | ||
| import { Json } from 'json-rpc-engine'; | ||
|
|
||
| import { UnrecognizedCaveatTypeError } from './errors'; | ||
| import { | ||
| AsyncRestrictedMethod, | ||
| RestrictedMethod, | ||
| PermissionConstraint, | ||
| RestrictedMethodParameters, | ||
| } from './Permission'; | ||
|
|
||
| export type CaveatConstraint = { | ||
| /** | ||
| * The type of the caveat. The type is presumed to be meaningful in the | ||
| * context of the capability it is associated with. | ||
| * | ||
| * In MetaMask, every permission can only have one caveat of each type. | ||
| */ | ||
| readonly type: string; | ||
|
|
||
| /** | ||
| * Any additional data necessary to enforce the caveat. | ||
| * | ||
| * TODO:TS4.4 Make optional | ||
| */ | ||
| readonly value: Json; | ||
| }; | ||
|
|
||
| /** | ||
| * TODO:docs | ||
| * | ||
| * @template Type - The type of the caveat. | ||
| * @template Value - The value associated with the caveat. | ||
| */ | ||
| export type Caveat<Type extends string, Value extends Json> = { | ||
| /** | ||
| * The type of the caveat. The type is presumed to be meaningful in the | ||
| * context of the capability it is associated with. | ||
| * | ||
| * In MetaMask, every permission can only have one caveat of each type. | ||
| */ | ||
| readonly type: Type; | ||
|
|
||
| /** | ||
| * Any additional data necessary to enforce the caveat. | ||
| * | ||
| * TODO:TS4.4 Make optional | ||
| */ | ||
| readonly value: Value; | ||
| }; | ||
|
|
||
| // Next, we define types used for specifying caveats at the consumer layer, | ||
| // and a function for applying caveats to a restricted method request. This is | ||
| // Accomplished by decorating the restricted method implementation with the | ||
| // the corresponding caveat functions. | ||
|
|
||
| /** | ||
| * A function for applying caveats to a restricted method request. | ||
| * | ||
| * @template ParentCaveat - The caveat type associated with this decorator. | ||
| * @param decorated - The restricted method implementation to be decorated. | ||
| * The method may have already been decorated with other caveats. | ||
| * @param caveat - The caveat object. | ||
| * @returns The decorate restricted method implementation. | ||
| */ | ||
| export type CaveatDecorator<ParentCaveat extends CaveatConstraint> = ( | ||
| decorated: AsyncRestrictedMethod<RestrictedMethodParameters, Json>, | ||
| caveat: ParentCaveat, | ||
| ) => AsyncRestrictedMethod<RestrictedMethodParameters, Json>; | ||
|
|
||
| /** | ||
| * TODO:docs | ||
| */ | ||
| type ExtractCaveatValueFromDecorator<Decorator extends CaveatDecorator<any>> = | ||
| Decorator extends ( | ||
| decorated: any, | ||
| caveat: infer ParentCaveat, | ||
| ) => AsyncRestrictedMethod<any, any> | ||
| ? ParentCaveat extends CaveatConstraint | ||
| ? ParentCaveat['value'] | ||
| : never | ||
| : never; | ||
|
|
||
| /** | ||
| * TODO:docs | ||
| */ | ||
| export type CaveatValidator<ParentCaveat extends CaveatConstraint> = ( | ||
| caveat: { type: ParentCaveat['type']; value: unknown }, | ||
| origin?: string, | ||
| target?: string, | ||
| ) => void; | ||
|
|
||
| /** | ||
| * TODO:docs | ||
| */ | ||
| export type CaveatSpecificationConstraint = { | ||
| /** | ||
| * The string type of the caveat. | ||
| */ | ||
| type: string; | ||
|
|
||
| /** | ||
| * The decorator function used to apply the caveat to restricted method | ||
| * requests. | ||
| */ | ||
| decorator: CaveatDecorator<any>; | ||
|
|
||
| /** | ||
| * The validator function used to validate caveats of the associated type | ||
| * whenever they are instantiated. Caveat are instantiated whenever they are | ||
| * created or mutated. | ||
| * | ||
| * The validator should throw an appropriate JSON-RPC error if validation fails. | ||
| * | ||
| * If no validator is specified, no validation of caveat values will be | ||
| * performed. Although caveats can also be validated by permission validators, | ||
| * validating caveat values separately is strongly recommended. | ||
| */ | ||
| validator?: CaveatValidator<any>; | ||
| }; | ||
|
|
||
| /** | ||
| * TODO:docs | ||
| */ | ||
| export type CaveatSpecification<SpecifiedCaveat extends CaveatConstraint> = { | ||
|
Gudahtt marked this conversation as resolved.
|
||
| /** | ||
| * The string type of the caveat. | ||
| */ | ||
| type: SpecifiedCaveat['type']; | ||
|
|
||
| /** | ||
| * The decorator function used to apply the caveat to restricted method | ||
| * requests. | ||
| */ | ||
| decorator: CaveatDecorator<SpecifiedCaveat>; | ||
|
|
||
| /** | ||
| * The validator function used to validate caveats of the associated type | ||
| * whenever they are instantiated. Caveat are instantiated whenever they are | ||
| * created or mutated. | ||
| * | ||
| * The validator should throw an appropriate JSON-RPC error if validation fails. | ||
| * | ||
| * If no validator is specified, no validation of caveat values will be | ||
| * performed. Although caveats can also be validated by permission validators, | ||
| * validating caveat values separately is strongly recommended. | ||
| */ | ||
| validator?: CaveatValidator<SpecifiedCaveat>; | ||
| }; | ||
|
|
||
| /** | ||
| * TODO:docs | ||
| */ | ||
| export type CaveatSpecificationMap< | ||
| Specification extends CaveatSpecificationConstraint, | ||
| > = Record<Specification['type'], Specification>; | ||
|
|
||
| // TODO: Figure out why we have to extend "any" in this type. | ||
| /** | ||
| * TODO:docs | ||
| */ | ||
| export type ExtractCaveats< | ||
| Specification extends CaveatSpecification<CaveatConstraint>, | ||
| > = Specification extends any | ||
|
rekmarks marked this conversation as resolved.
|
||
| ? Caveat< | ||
| Specification['type'], | ||
| ExtractCaveatValueFromDecorator<Specification['decorator']> | ||
| > | ||
| : never; | ||
|
|
||
| /** | ||
| * Extracts the type of a specific {@link Caveat} from a union of caveat | ||
| * specifications. | ||
| * | ||
| * @template CaveatSpecifications - The union of all caveat specifications. | ||
| * @template CaveatType - The type of the caveat to extract. | ||
| */ | ||
| export type ExtractCaveat< | ||
| CaveatSpecifications extends CaveatSpecification<CaveatConstraint>, | ||
| CaveatType extends string, | ||
| > = Extract<ExtractCaveats<CaveatSpecifications>, { type: CaveatType }>; | ||
|
|
||
| /** | ||
| * Extracts the value type of a specific {@link Caveat} from a union of caveat | ||
| * specifications. | ||
| * | ||
| * @template CaveatSpecifications - The union of all caveat specifications. | ||
| * @template CaveatType - The type of the caveat whose value to extract. | ||
| */ | ||
| export type ExtractCaveatValue< | ||
| CaveatSpecifications extends CaveatSpecification<CaveatConstraint>, | ||
| CaveatType extends string, | ||
| > = ExtractCaveat<CaveatSpecifications, CaveatType>['value']; | ||
|
|
||
| /** | ||
| * Decorate a restricted method implementation with its caveats. | ||
| * | ||
| * Note that all caveat functions (i.e. the argument and return value of the | ||
| * decorator) must be awaited. | ||
| */ | ||
| export function decorateWithCaveats< | ||
| CaveatSpecifications extends CaveatSpecification<CaveatConstraint>, | ||
| >( | ||
| methodImplementation: RestrictedMethod<RestrictedMethodParameters, Json>, | ||
| permission: Readonly<PermissionConstraint>, // bound to the requesting origin | ||
| caveatSpecifications: CaveatSpecificationMap<CaveatSpecifications>, // all caveat implementations | ||
| ): RestrictedMethod<RestrictedMethodParameters, Json> { | ||
| const { caveats } = permission; | ||
| if (!caveats) { | ||
| return methodImplementation; | ||
| } | ||
|
|
||
| let decorated = async ( | ||
| args: Parameters<RestrictedMethod<RestrictedMethodParameters, Json>>[0], | ||
| ) => methodImplementation(args); | ||
|
|
||
| for (const caveat of caveats) { | ||
| const specification = | ||
| caveatSpecifications[caveat.type as CaveatSpecifications['type']]; | ||
| if (!specification) { | ||
| throw new UnrecognizedCaveatTypeError(caveat.type); | ||
| } | ||
|
|
||
| decorated = specification.decorator(decorated, caveat); | ||
| } | ||
|
|
||
| return decorated; | ||
| } | ||
This file contains hidden or 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,45 @@ | ||
| import { constructPermission, CaveatConstraint } from '.'; | ||
|
|
||
| describe('constructPermission', () => { | ||
| it('constructs a permission', () => { | ||
| const invoker = 'foo.io'; | ||
| const target = 'wallet_bar'; | ||
|
|
||
| expect( | ||
| constructPermission({ | ||
| invoker, | ||
| target, | ||
| }), | ||
| ).toMatchObject( | ||
| expect.objectContaining({ | ||
| id: expect.any(String), | ||
| parentCapability: target, | ||
| invoker, | ||
| caveats: null, | ||
| date: expect.any(Number), | ||
| }), | ||
| ); | ||
| }); | ||
|
|
||
| it('constructs a permission with caveats', () => { | ||
| const invoker = 'foo.io'; | ||
| const target = 'wallet_bar'; | ||
| const caveats = [{ type: 'foo', value: 'bar' }] as [CaveatConstraint]; | ||
|
|
||
| expect( | ||
| constructPermission({ | ||
| invoker, | ||
| target, | ||
| caveats, | ||
| }), | ||
| ).toMatchObject( | ||
| expect.objectContaining({ | ||
| id: expect.any(String), | ||
| parentCapability: target, | ||
| invoker, | ||
| caveats: [...caveats], | ||
| date: expect.any(Number), | ||
| }), | ||
| ); | ||
| }); | ||
| }); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.