Skip to content

Commit

Permalink
PISTON-1161: Add feature schemas.
Browse files Browse the repository at this point in the history
* Adds feature specification and preference schemas.
* Adds kzd_users feature preference validation.
  • Loading branch information
Roger Neate committed Dec 15, 2020
1 parent 8edfd72 commit 84b6a7d
Show file tree
Hide file tree
Showing 10 changed files with 275 additions and 0 deletions.
1 change: 1 addition & 0 deletions applications/crossbar/doc/ref/users.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Key | Description | Type | Default | Required | Support Level
`enabled` | Determines if the user is currently enabled | `boolean()` | `true` | `false` | `supported`
`exempt_from_billing` | Indicates whether this user is exempt from billing | `boolean()` | | `false` |
`feature_level` | The user level for assigning feature sets | `string()` | | `false` |
`features` | Map of feature identifiers to user-specific preferences | `object()` | | `false` |
`first_name` | The first name of the user | `string(1..128)` | | `true` | `supported`
`flags.[]` | | `string()` | | `false` | `supported`
`flags` | Flags set by external applications | `array(string())` | | `false` | `supported`
Expand Down
1 change: 1 addition & 0 deletions applications/crossbar/doc/users.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ Key | Description | Type | Default | Required | Support Level
`enabled` | Determines if the user is currently enabled | `boolean()` | `true` | `false` | `supported`
`exempt_from_billing` | Indicates whether this user is exempt from billing | `boolean()` | | `false` |
`feature_level` | The user level for assigning feature sets | `string()` | | `false` |
`features` | Map of feature identifiers to user-specific preferences | `object()` | | `false` |
`first_name` | The first name of the user | `string(1..128)` | | `true` | `supported`
`flags.[]` | | `string()` | | `false` | `supported`
`flags` | Flags set by external applications | `array(string())` | | `false` | `supported`
Expand Down
89 changes: 89 additions & 0 deletions applications/crossbar/priv/api/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,20 @@
},
"type": "object"
},
"account_config.features": {
"description": "Schema for account-level feature specifications",
"properties": {
"specifications": {
"additionalProperties": {
"$ref": "#/definitions/feature.specification"
},
"default": {},
"description": "Map from feature identifiers to specifications",
"type": "object"
}
},
"type": "object"
},
"account_config.hero": {
"description": "Schema for hero account_config",
"properties": {
Expand Down Expand Up @@ -6793,6 +6807,60 @@
],
"type": "object"
},
"feature.preferences": {
"description": "Schema for a feature's user-specific preferences",
"properties": {
"enabled": {
"description": "Whether the feature should be enabled (i.e. visible) or disabled (i.e. hidden) in client applications",
"type": "boolean"
},
"props": {
"description": "A type-specific map of feature preference properties",
"type": "object"
}
},
"type": "object"
},
"feature.specification": {
"description": "Schema for a feature's definition and default preferences",
"properties": {
"definition": {
"additionalProperties": false,
"default": {},
"description": "The feature's definition",
"properties": {
"description": {
"description": "A description of the feature for display in client applications",
"type": "string"
},
"display_name": {
"description": "The name of the feature for display in client applications",
"type": "string"
},
"props": {
"default": {},
"description": "A type-specific map of feature definition properties",
"type": "object"
},
"type": {
"description": "The feature's type",
"type": "string"
}
},
"type": "object"
},
"enabled": {
"default": false,
"description": "Whether the feature should be enabled (i.e. visible) or disabled (i.e. hidden) in client applications by default",
"type": "boolean"
},
"props": {
"default": {},
"description": "A type-specific map of default feature preference properties",
"type": "object"
}
}
},
"find_numbers": {
"properties": {
"country": {
Expand Down Expand Up @@ -35024,6 +35092,20 @@
},
"type": "object"
},
"system_config.features": {
"description": "Schema for system-wide feature specifications",
"properties": {
"specifications": {
"additionalProperties": {
"$ref": "#/definitions/feature.specification"
},
"default": {},
"description": "Map from feature identifiers to specifications",
"type": "object"
}
},
"type": "object"
},
"system_config.frontier": {
"description": "Schema for frontier system_config",
"properties": {
Expand Down Expand Up @@ -39911,6 +39993,13 @@
"description": "The user level for assigning feature sets",
"type": "string"
},
"features": {
"additionalProperties": {
"$ref": "#/definitions/feature.preferences"
},
"description": "Map of feature identifiers to user-specific preferences",
"type": "object"
},
"first_name": {
"description": "The first name of the user",
"maxLength": 128,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"_id": "account_config.features",
"description": "Schema for account-level feature specifications",
"properties": {
"specifications": {
"additionalProperties": {
"$ref": "feature.specification"
},
"default": {},
"description": "Map from feature identifiers to specifications",
"type": "object"
}
},
"type": "object"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"_id": "feature.preferences",
"additionalProperties": false,
"description": "Schema for a feature's user-specific preferences",
"properties": {
"enabled": {
"description": "Whether the feature should be enabled (i.e. visible) or disabled (i.e. hidden) in client applications",
"type": "boolean"
},
"props": {
"description": "A type-specific map of feature preference properties",
"type": "object"
}
},
"type": "object"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"_id": "feature.specification",
"additionalProperties": false,
"description": "Schema for a feature's definition and default preferences",
"properties": {
"definition": {
"additionalProperties": false,
"default": {},
"description": "The feature's definition",
"properties": {
"description": {
"description": "A description of the feature for display in client applications",
"type": "string"
},
"display_name": {
"description": "The name of the feature for display in client applications",
"type": "string"
},
"props": {
"default": {},
"description": "A type-specific map of feature definition properties",
"type": "object"
},
"type": {
"description": "The feature's type",
"type": "string"
}
},
"type": "object"
},
"enabled": {
"default": false,
"description": "Whether the feature should be enabled (i.e. visible) or disabled (i.e. hidden) in client applications by default",
"type": "boolean"
},
"props": {
"default": {},
"description": "A type-specific map of default feature preference properties",
"type": "object"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"_id": "system_config.features",
"description": "Schema for system-wide feature specifications",
"properties": {
"specifications": {
"additionalProperties": {
"$ref": "feature.specification"
},
"default": {},
"description": "Map from feature identifiers to specifications",
"type": "object"
}
},
"type": "object"
}
7 changes: 7 additions & 0 deletions applications/crossbar/priv/couchdb/schemas/users.json
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,13 @@
"description": "The user level for assigning feature sets",
"type": "string"
},
"features": {
"additionalProperties": {
"$ref": "feature.preferences"
},
"description": "Map of feature identifiers to user-specific preferences",
"type": "object"
},
"first_name": {
"description": "The first name of the user",
"maxLength": 128,
Expand Down
72 changes: 72 additions & 0 deletions core/kazoo_documents/src/kzd_users.erl
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
-export([enabled/1, enabled/2, set_enabled/2]).
-export([exempt_from_billing/1, exempt_from_billing/2, set_exempt_from_billing/2]).
-export([feature_level/1, feature_level/2, set_feature_level/2]).
-export([features/1, features/2, set_features/2]).
-export([first_name/1, first_name/2, set_first_name/2]).
-export([flags/1, flags/2, set_flags/2]).
-export([formatters/1, formatters/2, set_formatters/2]).
Expand Down Expand Up @@ -93,6 +94,9 @@
-define(SCHEMA, <<"users">>).
-define(LIST_BY_USERNAME, <<"users/list_by_username">>).
-define(LIST_BY_HOTDESK_ID, <<"users/list_by_hotdesk_id">>).
-define(FEATURES_CONFIG_CAT, <<"features">>).
-define(FEATURE_CONFIG_SPECIFICATIONS_KEY, <<"specifications">>).
-define(FEATURE_PREFERENCES_KEY, <<"features">>).

-define(SYSCONFIG_CB_USERS, <<"crossbar.users">>).
-define(SHOULD_GENERATE_USER_PASSWORD_IF_EMPTY
Expand Down Expand Up @@ -442,6 +446,18 @@ feature_level(Doc, Default) ->
set_feature_level(Doc, FeatureLevel) ->
kz_json:set_value([<<"feature_level">>], FeatureLevel, Doc).

-spec features(doc()) -> kz_term:api_object().
features(Doc) ->
features(Doc, 'undefined').

-spec features(doc(), Default) -> kz_json:object() | Default.
features(Doc, Default) ->
kz_json:get_json_value([<<"features">>], Doc, Default).

-spec set_features(doc(), kz_json:object()) -> doc().
set_features(Doc, Features) ->
kz_json:set_value([<<"features">>], Features, Doc).

-spec first_name(doc()) -> kz_term:api_ne_binary().
first_name(Doc) ->
first_name(Doc, 'undefined').
Expand Down Expand Up @@ -1134,6 +1150,7 @@ validate(AuthAccountId, AccountId, UserId, ReqJObj) ->
%% this check must have the current doc
,fun maybe_rehash_creds/3
,fun verify_hero_apps/3
,fun maybe_validate_features/3
,fun validate_call_forward/3
],
try do_validation(AccountId, UserId, ReqJObj, ValidateFuns) of
Expand Down Expand Up @@ -1708,6 +1725,61 @@ get_available_hero_apps(AccountId) ->
,kzd_hero:get_apps(AccountId)
).

%%------------------------------------------------------------------------------
%% @doc If any feature preferences are specified, ensure each feature key
%% has a corresponding specification.
%% @end
%%------------------------------------------------------------------------------
-spec maybe_validate_features(kz_term:api_ne_binary(), kz_term:api_ne_binary(), kazoo_documents:doc_validation_acc()) ->
kazoo_documents:doc_validation_acc().
maybe_validate_features(AccountId, _UserId, {Doc, _Errors}=ValidateAcc) ->
lager:debug("validating feature preferences"),
case features(Doc) of
'undefined' -> ValidateAcc;
FeaturePrefs -> validate_features(AccountId, FeaturePrefs, ValidateAcc)
end.

%%------------------------------------------------------------------------------
%% @doc For any specified feature preferences, ensure each feature key
%% has a corresponding specification.
%% @end
%%------------------------------------------------------------------------------
-spec validate_features(kz_term:api_ne_binary(), kz_json:object(), kazoo_documents:doc_validation_acc()) ->
kazoo_documents:doc_validation_acc().
validate_features(AccountId, FeaturePrefs, ValidateAcc) ->
FeatureSpecs = get_feature_specifications(AccountId),
FolderFun = fun(Key, _Value, Acc) ->
FeatureSpec = kz_json:get_value(Key, FeatureSpecs),
validate_feature(Key, FeatureSpec, Acc)
end,
kz_json:foldl(FolderFun, ValidateAcc, FeaturePrefs).

%%------------------------------------------------------------------------------
%% @doc Ensures a feature preferences key has a corresponding specification.
%% @end
%%------------------------------------------------------------------------------
-spec validate_feature(kz_json:key(), kz_json:object() | 'undefined', kazoo_documents:doc_validation_acc()) ->
kazoo_documents:doc_validation_acc().
validate_feature(FeatureKey, 'undefined', {Doc, Errors}) ->
Msg = kz_json:from_list(
[{<<"message">>, <<"A feature preferences key references an undefined specification">>}
,{<<"cause">>, FeatureKey}
]),
{Doc, [{[?FEATURE_PREFERENCES_KEY], <<"invalid">>, Msg} | Errors]};
validate_feature(_FeatureKey, _FeatureSpec, ValidateAcc) ->
ValidateAcc.

%%------------------------------------------------------------------------------
%% @doc Gets feature specifications from merged account/system configuration.
%% Important: This function must match the configuration merge behavior of the
%% `cb_configs:set_config_to_context/2' function.
%% @end
%%------------------------------------------------------------------------------
-spec get_feature_specifications(kz_term:api_ne_binary()) -> kz_json:json_term() | 'undefined'.
get_feature_specifications(AccountId) ->
FeaturesConfig = kapps_config_util:get_config(AccountId, ?FEATURES_CONFIG_CAT),
kz_json:get_value(?FEATURE_CONFIG_SPECIFICATIONS_KEY, FeaturesConfig, kz_json:new()).

%%------------------------------------------------------------------------------
%% @doc Verify that if call forward is enabled, the call forward number is non-
%% empty.
Expand Down
13 changes: 13 additions & 0 deletions core/kazoo_documents/src/kzd_users.erl.src
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
-export([enabled/1, enabled/2, set_enabled/2]).
-export([exempt_from_billing/1, exempt_from_billing/2, set_exempt_from_billing/2]).
-export([feature_level/1, feature_level/2, set_feature_level/2]).
-export([features/1, features/2, set_features/2]).
-export([first_name/1, first_name/2, set_first_name/2]).
-export([flags/1, flags/2, set_flags/2]).
-export([formatters/1, formatters/2, set_formatters/2]).
Expand Down Expand Up @@ -404,6 +405,18 @@ feature_level(Doc, Default) ->
set_feature_level(Doc, FeatureLevel) ->
kz_json:set_value([<<"feature_level">>], FeatureLevel, Doc).

-spec features(doc()) -> kz_term:api_object().
features(Doc) ->
features(Doc, 'undefined').

-spec features(doc(), Default) -> kz_json:object() | Default.
features(Doc, Default) ->
kz_json:get_json_value([<<"features">>], Doc, Default).

-spec set_features(doc(), kz_json:object()) -> doc().
set_features(Doc, Features) ->
kz_json:set_value([<<"features">>], Features, Doc).

-spec first_name(doc()) -> kz_term:api_ne_binary().
first_name(Doc) ->
first_name(Doc, 'undefined').
Expand Down

0 comments on commit 84b6a7d

Please sign in to comment.