TT#22422 Implement capabilities endpoint

Used to fetch whether pbx, faxserver, rtcengine etc is enabled.

* Fix rtcengine reseller creation:
  - Refuse to create reseller if rtcengine part fails
  - Use proper auto-generated rtcengine values (pass, domain)
    to not fail on reseller names which are not forming valid
    hostnames.

* Implement /api/admins/id to support testing
  - add test
  - fix creating admins with overly long login
  - fix various ACL bugs handling /api/admins/
  - fix creating /api/admincerts/ as r/o user

* Fix test framework
  - use proper client cert when switching API user

Change-Id: I602fdd8181c0b3f23e76e3eab0df90a1ff9e986f
(cherry picked from commit 0d376bd8b59db65296090d26f2c84d704129beef)
changes/14/15614/9
Andreas Granig 8 years ago
parent 7482153f3f
commit a52558b411

@ -33,7 +33,12 @@ sub return_representation_post {}
sub create_item {
my ($self, $c, $resource, $form, $process_extras) = @_;
my $login = $resource->{login} // $c->user->login;
my $login;
if($c->user->read_only) {
$login = $c->user->login;
} else {
$login = $resource->{login} // $c->user->login;
}
my $item_rs = $self->get_list($c);
my $admin = $item_rs->search({
login => $login,
@ -49,6 +54,7 @@ sub create_item {
# b. you're a master
# c. you're a superuser
unless($c->user->login eq $login || $c->user->is_master || $c->user->is_superuser) {
$c->log->warn("Admin " . $c->user->login . " trying to create certs for user $login, reject");
$self->error($c, HTTP_FORBIDDEN, "Insufficient privileges to create certificate for this administrator");
return;
}

@ -162,6 +162,11 @@ sub POST :Allow {
my $guard = $c->model('DB')->txn_scope_guard;
{
unless($c->user->is_master) {
$self->error($c, HTTP_FORBIDDEN, "Cannot create admin without master permissions");
last;
}
my $resource = $self->get_valid_post_data(
c => $c,
media_type => 'application/json',

@ -0,0 +1,157 @@
package NGCP::Panel::Controller::API::AdminsItem;
use NGCP::Panel::Utils::Generic qw(:all);
use Sipwise::Base;
use boolean qw(true);
use NGCP::Panel::Utils::DataHal qw();
use NGCP::Panel::Utils::DataHalLink qw();
use HTTP::Headers qw();
use HTTP::Status qw(:constants);
require Catalyst::ActionRole::ACL;
require NGCP::Panel::Role::HTTPMethods;
require Catalyst::ActionRole::RequireSSL;
sub allowed_methods{
return [qw/GET OPTIONS HEAD DELETE/];
}
use parent qw/Catalyst::Controller NGCP::Panel::Role::API::Admins/;
sub resource_name{
return 'admins';
}
sub dispatch_path{
return '/api/admins/';
}
sub relation{
return 'http://purl.org/sipwise/ngcp-api/#rel-admins';
}
sub journal_query_params {
my($self,$query_params) = @_;
return $self->get_journal_query_params($query_params);
}
__PACKAGE__->config(
action => {
(map { $_ => {
ACLDetachTo => '/api/root/invalid_user',
AllowedRole => [qw/admin reseller/],
Args => 1,
Does => [qw(ACL RequireSSL)],
Method => $_,
Path => __PACKAGE__->dispatch_path,
} } @{ __PACKAGE__->allowed_methods }),
@{ __PACKAGE__->get_journal_action_config(__PACKAGE__->resource_name,{
ACLDetachTo => '/api/root/invalid_user',
AllowedRole => [qw/admin reseller/],
Does => [qw(ACL RequireSSL)],
}) },
},
);
sub gather_default_action_roles {
my ($self, %args) = @_; my @roles = ();
push @roles, 'NGCP::Panel::Role::HTTPMethods' if $args{attributes}->{Method};
return @roles;
}
sub auto :Private {
my ($self, $c) = @_;
$self->set_body($c);
$self->log_request($c);
return 1;
}
sub GET :Allow {
my ($self, $c, $id) = @_;
{
last unless $self->valid_id($c, $id);
my $admin = $self->item_by_id($c, $id);
last unless $self->resource_exists($c, admin => $admin);
my $hal = $self->hal_from_item($c, $admin);
my $response = HTTP::Response->new(HTTP_OK, undef, HTTP::Headers->new(
(map { # XXX Data::HAL must be able to generate links with multiple relations
s|rel="(http://purl.org/sipwise/ngcp-api/#rel-resellers)"|rel="item $1"|r =~
s/rel=self/rel="item self"/r;
} $hal->http_headers),
), $hal->as_json);
$c->response->headers($response->headers);
$c->response->body($response->content);
return;
}
return;
}
sub HEAD :Allow {
my ($self, $c, $id) = @_;
$c->forward(qw(GET));
$c->response->body(q());
return;
}
sub OPTIONS :Allow {
my ($self, $c, $id) = @_;
my $allowed_methods = $self->allowed_methods_filtered($c);
$c->response->headers(HTTP::Headers->new(
Allow => join(', ', @{ $allowed_methods }),
Accept_Patch => 'application/json-patch+json',
));
$c->response->content_type('application/json');
$c->response->body(JSON::to_json({ methods => $allowed_methods })."\n");
return;
}
sub DELETE :Allow {
my ($self, $c, $id) = @_;
my $guard = $c->model('DB')->txn_scope_guard;
{
my $admin = $self->item_by_id($c, $id);
last unless $self->resource_exists($c, admin => $admin);
$c->log->error("++++++ trying to delete admin #$id as #" . $c->user->id);
if($c->user->id == $id) {
$self->error($c, HTTP_FORBIDDEN, "Cannot delete own user");
last;
}
if($c->user->read_only) {
$self->error($c, HTTP_FORBIDDEN, "Insufficient permissions");
last;
}
# reseller association is checked in item_rs of role
last unless $self->add_delete_journal_item_hal($c,sub {
my $self = shift;
my ($c) = @_;
return $self->hal_from_item($c,$admin); });
$admin->delete;
$guard->commit;
$c->response->status(HTTP_NO_CONTENT);
$c->response->body(q());
}
return;
}
sub get_journal_methods{
return [qw/handle_item_base_journal handle_journals_get handle_journalsitem_get handle_journals_options handle_journalsitem_options handle_journals_head handle_journalsitem_head/];
}
sub end : Private {
my ($self, $c) = @_;
$self->log_response($c);
return;
}
1;
# vim: set tabstop=4 expandtab:

@ -0,0 +1,154 @@
package NGCP::Panel::Controller::API::Capabilities;
use NGCP::Panel::Utils::Generic qw(:all);
use Sipwise::Base;
use boolean qw(true);
use NGCP::Panel::Utils::DataHal qw();
use NGCP::Panel::Utils::DataHalLink qw();
use HTTP::Headers qw();
use HTTP::Status qw(:constants);
use NGCP::Panel::Utils::DateTime;
use Path::Tiny qw(path);
use Safe::Isa qw($_isa);
require Catalyst::ActionRole::ACL;
require Catalyst::ActionRole::CheckTrailingSlash;
require NGCP::Panel::Role::HTTPMethods;
require Catalyst::ActionRole::RequireSSL;
sub allowed_methods{
return [qw/GET OPTIONS HEAD/];
}
sub api_description {
return 'Lists the capabilities/features enabled for the access role.'
};
sub query_params {
return [
{
param => 'name',
description => 'Filter for capability name.',
},
];
}
use parent qw/Catalyst::Controller NGCP::Panel::Role::API::Capabilities/;
sub resource_name{
return 'capabilities';
}
sub dispatch_path{
return '/api/capabilities/';
}
sub relation{
return 'http://purl.org/sipwise/ngcp-api/#rel-capabilities';
}
__PACKAGE__->config(
action => {
map { $_ => {
ACLDetachTo => '/api/root/invalid_user',
AllowedRole => [qw/admin reseller subscriberadmin subscriber/],
Args => 0,
Does => [qw(ACL CheckTrailingSlash RequireSSL)],
Method => $_,
Path => __PACKAGE__->dispatch_path,
} } @{ __PACKAGE__->allowed_methods },
},
);
sub gather_default_action_roles {
my ($self, %args) = @_; my @roles = ();
push @roles, 'NGCP::Panel::Role::HTTPMethods' if $args{attributes}->{Method};
return @roles;
}
sub auto :Private {
my ($self, $c) = @_;
$self->set_body($c);
$self->log_request($c);
return 1;
}
sub GET :Allow {
my ($self, $c) = @_;
my $page = $c->request->params->{page} // 1;
my $rows = $c->request->params->{rows} // 10;
{
my $capabilities = $self->item_rs($c);
(my $total_count, $capabilities) = $self->paginate_order_collection($c, $capabilities);
my (@embedded, @links);
my $form = $self->get_form($c);
for my $cap (@{ $capabilities }) {
push @embedded, $self->hal_from_item($c, $cap, $form);
push @links, NGCP::Panel::Utils::DataHalLink->new(
relation => 'ngcp:'.$self->resource_name,
href => sprintf('/%s%d', $c->request->path, $cap->{id}),
);
}
push @links,
NGCP::Panel::Utils::DataHalLink->new(
relation => 'curies',
href => 'http://purl.org/sipwise/ngcp-api/#rel-{rel}',
name => 'ngcp',
templated => true,
),
NGCP::Panel::Utils::DataHalLink->new(relation => 'profile', href => 'http://purl.org/sipwise/ngcp-api/'),
NGCP::Panel::Utils::DataHalLink->new(relation => 'self', href => sprintf('/%s?page=%s&rows=%s', $c->request->path, $page, $rows));
if(($total_count / $rows) > $page ) {
push @links, NGCP::Panel::Utils::DataHalLink->new(relation => 'next', href => sprintf('/%s?page=%d&rows=%d', $c->request->path, $page + 1, $rows));
}
if($page > 1) {
push @links, NGCP::Panel::Utils::DataHalLink->new(relation => 'prev', href => sprintf('/%s?page=%d&rows=%d', $c->request->path, $page - 1, $rows));
}
my $hal = NGCP::Panel::Utils::DataHal->new(
embedded => [@embedded],
links => [@links],
);
$hal->resource({
total_count => $total_count,
});
my $rname = $self->resource_name;
my $response = HTTP::Response->new(HTTP_OK, undef,
HTTP::Headers->new($hal->http_headers(skip_links => 1)), $hal->as_json);
$c->response->headers($response->headers);
$c->response->body($response->content);
return;
}
return;
}
sub HEAD :Allow {
my ($self, $c) = @_;
$c->forward(qw(GET));
$c->response->body(q());
return;
}
sub OPTIONS :Allow {
my ($self, $c) = @_;
my $allowed_methods = $self->allowed_methods_filtered($c);
$c->response->headers(HTTP::Headers->new(
Allow => join(', ', @{ $allowed_methods }),
Accept_Post => 'application/hal+json; profile=http://purl.org/sipwise/ngcp-api/#rel-'.$self->resource_name,
));
$c->response->content_type('application/json');
$c->response->body(JSON::to_json({ methods => $allowed_methods })."\n");
return;
}
sub end : Private {
my ($self, $c) = @_;
$self->log_response($c);
return;
}
1;
# vim: set tabstop=4 expandtab:

@ -0,0 +1,102 @@
package NGCP::Panel::Controller::API::CapabilitiesItem;
use NGCP::Panel::Utils::Generic qw(:all);
use Sipwise::Base;
use boolean qw(true);
use NGCP::Panel::Utils::DataHal qw();
use NGCP::Panel::Utils::DataHalLink qw();
use HTTP::Headers qw();
use HTTP::Status qw(:constants);
require Catalyst::ActionRole::ACL;
require NGCP::Panel::Role::HTTPMethods;
require Catalyst::ActionRole::RequireSSL;
sub allowed_methods{
return [qw/GET OPTIONS HEAD/];
}
use parent qw/Catalyst::Controller NGCP::Panel::Role::API::Capabilities/;
sub resource_name{
return 'capabilities';
}
sub dispatch_path{
return '/api/capabilities/';
}
sub relation{
return 'http://purl.org/sipwise/ngcp-api/#rel-capabilities';
}
__PACKAGE__->config(
action => {
(map { $_ => {
ACLDetachTo => '/api/root/invalid_user',
AllowedRole => [qw/admin reseller subscriberadmin subscriber/],
Args => 1,
Does => [qw(ACL RequireSSL)],
Method => $_,
Path => __PACKAGE__->dispatch_path,
} } @{ __PACKAGE__->allowed_methods }),
},
);
sub gather_default_action_roles {
my ($self, %args) = @_; my @roles = ();
push @roles, 'NGCP::Panel::Role::HTTPMethods' if $args{attributes}->{Method};
return @roles;
}
sub auto :Private {
my ($self, $c) = @_;
$self->set_body($c);
$self->log_request($c);
return 1;
}
sub GET :Allow {
my ($self, $c, $id) = @_;
{
last unless $self->valid_id($c, $id);
my $item = $self->item_by_id($c, $id);
last unless $self->resource_exists($c, capability => $item);
my $hal = $self->hal_from_item($c, $item);
my $response = HTTP::Response->new(HTTP_OK, undef, HTTP::Headers->new(
(map { # XXX Data::HAL must be able to generate links with multiple relations
s|rel="(http://purl.org/sipwise/ngcp-api/#rel-resellers)"|rel="item $1"|r =~
s/rel=self/rel="item self"/r;
} $hal->http_headers),
), $hal->as_json);
$c->response->headers($response->headers);
$c->response->body($response->content);
return;
}
return;
}
sub HEAD :Allow {
my ($self, $c, $id) = @_;
$c->forward(qw(GET));
$c->response->body(q());
return;
}
sub OPTIONS :Allow {
my ($self, $c, $id) = @_;
my $allowed_methods = $self->allowed_methods_filtered($c);
$c->response->headers(HTTP::Headers->new(
Allow => join(', ', @{ $allowed_methods }),
Accept_Patch => 'application/json-patch+json',
));
$c->response->content_type('application/json');
$c->response->body(JSON::to_json({ methods => $allowed_methods })."\n");
return;
}
1;
# vim: set tabstop=4 expandtab:

@ -202,6 +202,7 @@ sub POST :Allow {
status => $resource->{status},
contract_id => $resource->{contract_id},
});
my $rtcfail = 0;
NGCP::Panel::Utils::Reseller::create_email_templates( c => $c, reseller => $reseller );
NGCP::Panel::Utils::Rtc::modify_reseller_rtc(
resource => $resource,
@ -211,8 +212,10 @@ sub POST :Allow {
my ($msg, $debug) = @_;
$c->log->debug($debug) if $debug;
$c->log->warn($msg);
$rtcfail = 1;
return;
});
die "failed to create rtcengine reseller" if($rtcfail);
} catch($e) {
$c->log->error("failed to create reseller: $e"); # TODO: user, message, trace, ...
$self->error($c, HTTP_INTERNAL_SERVER_ERROR, "Failed to create reseller.");

@ -166,7 +166,11 @@ sub GET : Allow {
$item_rs = $full_mod->item_rs($c, "");
}
if ($item_rs) {
$sorting_cols = [$item_rs->result_source->columns];
if(ref $item_rs eq "ARRAY") {
$sorting_cols = [map { $_->{name} } @{ $item_rs }];
} else {
$sorting_cols = [$item_rs->result_source->columns];
}
}
}
my ($form_fields,$form_fields_upload) = $form ? $self->get_collection_properties($form) : ([],[]);

@ -95,9 +95,13 @@ sub auto :Private {
} else {
$c->log->debug("++++++ admin '".$c->user->login."' authenticated via api_admin_cert");
}
if($c->user->read_only && !($c->req->method =~ /^(GET|HEAD|OPTIONS)$/)) {
if($c->user->read_only && $c->req->method eq "POST" &&
$c->req->uri->path =~ m|^/api/admincerts/$|) {
$c->log->info("let read-only user '".$c->user->login."' generate admin cert for itself");
} elsif($c->user->read_only && !($c->req->method =~ /^(GET|HEAD|OPTIONS)$/)) {
$c->log->error("invalid method '".$c->req->method."' for read-only user '".$c->user->login."', rejecting");
$c->user->logout;
$c->log->error("++++ body data: " . $c->req->body_data);
$c->response->status(403);
$c->res->body(JSON::to_json({
message => "Invalid HTTP method for read-only user",
@ -180,9 +184,13 @@ sub auto :Private {
} else {
$c->log->debug("++++++ admin '".$c->user->login."' authenticated via api_admin_http");
}
if($c->user->read_only && !($c->req->method =~ /^(GET|HEAD|OPTIONS)$/)) {
if($c->user->read_only && $c->req->method eq "POST" &&
$c->req->uri->path =~ m|^/api/admincerts/$|) {
$c->log->info("let read-only user '".$c->user->login."' generate admin cert for itself");
} elsif($c->user->read_only && !($c->req->method =~ /^(GET|HEAD|OPTIONS)$/)) {
$c->log->error("invalid method '".$c->req->method."' for read-only user '".$c->user->login."', rejecting");
$c->user->logout;
$c->log->error("++++ body data: " . $c->req->body_data);
$c->response->status(403);
$c->res->body(JSON::to_json({
message => "Invalid HTTP method for read-only user",

@ -9,7 +9,7 @@ has_field 'submitid' => ( type => 'Hidden' );
sub build_render_list {[qw/submitid fields actions/]}
sub build_form_element_class {[qw(form-horizontal)]}
has_field 'login' => (type => 'Text', required => 1, minlength => 5);
has_field 'login' => (type => 'Text', required => 1, minlength => 5, maxlength => 31);
has_field 'password' => (type => 'Password', required => 1, label => 'Password');
for (qw(is_active show_passwords call_data billing_data)) {
has_field $_ => (type => 'Boolean', default => 1);

@ -0,0 +1,32 @@
package NGCP::Panel::Form::Capabilities::API;
use HTML::FormHandler::Moose;
extends 'HTML::FormHandler';
has_field 'id' => (
type => 'PosInteger',
element_attr => {
rel => ['tooltip'],
title => ['The id of the capability.']
},
);
has_field 'name' => (
type => 'Text',
element_attr => {
rel => ['tooltip'],
title => ['The name of the capability.']
},
);
has_field 'enabled' => (
type => 'Boolean',
element_attr => {
rel => ['tooltip'],
title => ['Whether the capability is enabled.']
},
);
1;
# vim: set tabstop=4 expandtab:

@ -25,7 +25,7 @@ has_field 'description' => (
#not_nullable => 1, in the future?
element_attr => {
rel => ['tooltip'],
title => ['Arbitrary text.'],
title => ['The description of the Subscriber Profile.'],
},
);
@ -35,7 +35,7 @@ has_field 'set_default' => (
required => 0,
element_attr => {
rel => ['tooltip'],
title => ['Make this profile automatically the default profile for new subscribers having this profile set.'],
title => ['Make this profile automatically the default profile for new subscribers having assigned the Profile Set this profile belongs.'],
},
);
@ -43,6 +43,10 @@ has_field 'attribute' => (
type => 'Compound',
label => 'Attributes',
#do_label => 1,
element_attr => {
rel => ['tooltip'],
title => ['An array of subscriber preference names the subscriber can control.'],
},
);
has_field 'save' => (

@ -22,6 +22,16 @@ sub _item_rs {
reseller_id => $c->user->reseller_id
});
}
if($c->user->is_master || $c->user->is_superuser) {
# return all (or all of reseller) admins
} else {
# otherwise, only return the own admin if master is not set
$item_rs = $item_rs->search({
id => $c->user->id,
});
}
return $item_rs;
}
@ -43,6 +53,8 @@ sub hal_from_item {
delete $resource{md5pass};
delete $resource{saltedpass};
my $adm = $c->user->roles eq "admin";
my $hal = NGCP::Panel::Utils::DataHal->new(
links => [
NGCP::Panel::Utils::DataHalLink->new(
@ -54,7 +66,7 @@ sub hal_from_item {
NGCP::Panel::Utils::DataHalLink->new(relation => 'collection', href => sprintf('%s', $self->dispatch_path)),
NGCP::Panel::Utils::DataHalLink->new(relation => 'profile', href => 'http://purl.org/sipwise/ngcp-api/'),
NGCP::Panel::Utils::DataHalLink->new(relation => 'self', href => sprintf("%s%d", $self->dispatch_path, $item->id)),
NGCP::Panel::Utils::DataHalLink->new(relation => 'ngcp:resellers', href => sprintf("/api/resellers/%d", $item->reseller_id)),
$adm ? NGCP::Panel::Utils::DataHalLink->new(relation => 'ngcp:resellers', href => sprintf("/api/resellers/%d", $item->reseller_id)) : (),
$self->get_journal_relation_link($item->id),
],
relation => 'ngcp:'.$self->resource_name,

@ -0,0 +1,137 @@
package NGCP::Panel::Role::API::Capabilities;
use NGCP::Panel::Utils::Generic qw(:all);
use Sipwise::Base;
use parent 'NGCP::Panel::Role::API';
use boolean qw(true);
use NGCP::Panel::Utils::DataHal qw();
use NGCP::Panel::Utils::DataHalLink qw();
use HTTP::Status qw(:constants);
use JSON::Types;
use NGCP::Panel::Utils::Contract;
use NGCP::Panel::Form::Capabilities::API;
sub get_form {
my ($self, $c) = @_;
return NGCP::Panel::Form::Capabilities::API->new(ctx => $c);
}
sub hal_from_item {
my ($self, $c, $item, $form) = @_;
my $hal = NGCP::Panel::Utils::DataHal->new(
links => [
NGCP::Panel::Utils::DataHalLink->new(
relation => 'curies',
href => 'http://purl.org/sipwise/ngcp-api/#rel-{rel}',
name => 'ngcp',
templated => true,
),
NGCP::Panel::Utils::DataHalLink->new(relation => 'collection', href => sprintf("/api/%s/", $self->resource_name)),
NGCP::Panel::Utils::DataHalLink->new(relation => 'profile', href => 'http://purl.org/sipwise/ngcp-api/'),
NGCP::Panel::Utils::DataHalLink->new(relation => 'self', href => sprintf("%s%d", $self->dispatch_path, $item->{id})),
],
relation => 'ngcp:'.$self->resource_name,
);
$form //= $self->get_form($c);
$self->validate_form(
c => $c,
resource => $item,
form => $form,
run => 0,
);
$hal->resource($item);
return $hal;
}
sub _item_rs {
my ($self, $c) = @_;
my ($cloudpbx, $sms, $faxserver, $rtcengine, $fileshare, $mobilepush);
$cloudpbx = $c->config->{features}->{cloudpbx} // 0;
$sms = $c->config->{features}->{sms} // 0;
$faxserver = $c->config->{features}->{faxserver} // 0;
$rtcengine = $c->config->{features}->{rtcengine} // 0;
$fileshare = $c->config->{features}->{fileshare} // 0;
$mobilepush = $c->config->{features}->{mobilepush} // 0;
if($c->user->roles eq "admin") {
# nothing to be done
} elsif($c->user->roles eq "reseller") {
# TODO: is it correct to just check rtc_user of reseller?
$rtcengine &= ($c->user->reseller->rtc_user // 0);
} else {
$rtcengine &= ($c->user->voip_subscriber->contract->contact->reseller->rtc_user // 0);
my $customer_rs = NGCP::Panel::Utils::Contract::get_customer_rs(
c => $c,
contract_id => $c->user->account_id,
);
$customer_rs = $customer_rs->search({
'-or' => [
'product.class' => 'sipaccount',
'product.class' => 'pbxaccount',
],
},{
'+select' => [ 'product.class' ],
'+as' => [ 'product_class' ],
});
my $customer = $customer_rs->first;
my $cpbx = ($customer->get_column('product_class') eq 'pbxaccount') ? 1 : 0;
$cloudpbx &= $cpbx;
# TODO: sms and rtcengine are not specially restricted; should it?
my $profile = $c->user->voip_subscriber_profile;
if($profile) {
my $attrs = [ map { $_->attribute->attribute } $profile->profile_attributes->all ];
if(grep(/^fax_server$/, @{ $attrs })) {
$faxserver &= 1;
} else {
$faxserver = 0;
}
}
}
my $item_rs = [
{ id => 1, name => 'cloudpbx', enabled => $cloudpbx },
{ id => 2, name => 'sms', enabled => $sms },
{ id => 3, name => 'faxserver', enabled => $faxserver },
{ id => 4, name => 'rtcengine', enabled => $rtcengine },
{ id => 5, name => 'fileshare', enabled => $fileshare},
{ id => 6, name => 'mobilepush',enabled => $mobilepush},
];
if($c->req->param('name')) {
my $res = [];
foreach my $item (@{ $item_rs }) {
if($item->{name} eq $c->req->param('name')) {
push @{ $res }, $item;
last;
}
}
return $res;
}
return $item_rs;
}
sub item_by_id {
my ($self, $c, $id) = @_;
my $item_rs = $self->item_rs($c);
foreach my $item(@{ $item_rs }) {
return $item if $item->{id} == $id;
}
return;
}
1;
# vim: set tabstop=4 expandtab:

@ -4,6 +4,7 @@ use warnings;
use strict;
use JSON qw//;
use UUID;
use NGCP::Panel::Utils::ComxAPIClient;
use NGCP::Panel::Utils::Generic qw/compare/;
@ -89,9 +90,15 @@ sub _create_rtc_user {
return unless &{$err_code}(
'Rtc Login failed. Check config settings. Status code: ' . $comx->login_status->{code}, $comx->login_status->{debug});
}
my ($uuid_bin, $uuid);
UUID::generate($uuid_bin);
UUID::unparse($uuid_bin, $uuid);
my $rand = get_random(10, $err_code);
return unless $rand;
my $pass = unpack("H*", $rand);
my $user = $comx->create_user(
$reseller_name . '@ngcp.com',
$reseller_name . 'pass12345',
$uuid . '@ngcp.local',
$pass,
);
if ($user->{code} != 201) {
return unless &{$err_code}(
@ -105,8 +112,8 @@ sub _create_rtc_user {
# 4. create related app
my $app = $comx->create_app(
$reseller_name . '_default_app',
$reseller_name . '.www.sipwise.com',
$uuid . '_default_app',
$uuid . '.sipwise.local',
$user->{data}{id},
);
if ($app->{code} != 201) {
@ -419,6 +426,8 @@ sub get_rtc_subscriber_data {
unless ($rtc_session) {
return {enable_rtc => 0}; # JSON::false ?
}
# TODO: huh? is this the right browser token?
return {enable_rtc => 1, rtc_browser_token => 'abcde TODO'};
}
@ -720,6 +729,19 @@ sub get_rtc_session {
return $session;
}
sub get_random {
my ($num, $err_code) = @_;
my ($fd, $buf);
unless(open($fd, '<', '/dev/urandom')) {
return unless &{$err_code}("Failed to open /dev/urandom: $!");
}
unless(read($fd, $buf, $num) == $num) {
return unless &{$err_code}("Failed to read $num bytes from /dev/urandom: $!");
}
close($fd);
return $buf;
}
1;
# vim: set tabstop=4 expandtab:

@ -0,0 +1,384 @@
#!/usr/bin/perl -w
use strict;
use NGCP::Test;
use Test::More;
use Clone 'clone';
use JSON qw/from_json to_json/;
use Data::Dumper;
my $test = NGCP::Test->new(log_debug => 0);
my $t = $test->generate_sid();
my $c_admin = $test->client();
my $ref = $test->reference_data(
client => $c_admin,
use_persistent => 1,
delete_persistent => 0,
depends => [
{
resource => 'admins',
hints => [{ name => 'superuser admin' }],
name => 'my_super_admin'
},
{
resource => 'admins',
hints => [{ name => 'readonly admin' }],
name => 'my_ro_admin'
},
{
resource => 'admins',
hints => [{ name => 'master reseller admin' }],
name => 'my_reseller_admin'
},
{
resource => 'admins',
hints => [{ name => 'nomaster reseller admin' }],
name => 'my_std_admin'
},
],
);
my $sid = $ref->sid();
my $c_su_admin = $test->client(
role => 'admin',
username => $ref->data('my_super_admin')->{login},
password => "superadmin_$sid",
);
my $c_ro_admin = $test->client(
role => 'admin',
username => $ref->data('my_ro_admin')->{login},
password => "roadmin_$sid",
);
my $c_res_admin = $test->client(
role => 'admin',
username => $ref->data('my_reseller_admin')->{login},
password => "mstreselleradmin_$sid",
);
my $c_std_admin = $test->client(
role => 'admin',
username => $ref->data('my_std_admin')->{login},
password => "stdreselleradmin_$sid",
);
### let's roll
my $adm_res = $test->resource(
client => $c_su_admin,
resource => 'admins',
data => {
login => "testadmin_$sid",
password => "testadmin_$sid",
reseller_id => $ref->data('my_super_admin')->{reseller_id},
},
);
# superuser can fetch all admins
my $admins = $adm_res->test_get(
name => 'fetch all admins as superuser',
expected_links => [qw/
ngcp:resellers
/],
expected_result => { code => '200' }
);
# superuser can create admin in own reseller
# superuser can create admin in other reseller
# superuser can't create admin with login > 31 chars
$adm_res->test_post(
name => 'create reseller as su admin',
data_replace => [
{},
[
{ field => 'reseller_id', value => $ref->data('my_reseller_admin')->{reseller_id} },
{ field => 'login', value => "o_res_admin_$sid" }
],
{ field => 'login', value => "some_overly_long_admin_$sid" }
],
skip_test_fields => [qw/password/],
expected_result => [
{ code => '201' },
{ code => '201' },
{ code => '422', error_re => 'Field should not exceed 31 characters' }
],
);
my $adm = $adm_res->pop_created_item();
# superuser can update admin in own reseller
$adm_res->test_put(
name => 'update own reseller admin as su admin',
item => $adm,
data_replace => {
field => 'login', value => "s_up_admin_$sid"
},
skip_test_fields => [qw/password/],
expected_result => { code => '404' }
);
# superuser can delete admin in own reseller
$adm_res->test_delete(
name => 'delete own reseller admin as su admin',
item => $adm,
expected_result => { code => '204' }
);
$adm = $adm_res->pop_created_item();
# superuser can update admin in other reseller
$adm_res->test_put(
name => 'update other reseller admin as su admin',
item => $adm,
data_replace => {
field => 'login', value => "another_updated_admin_$sid"
},
expected_result => { code => '404' }
);
# superuser can delete admin in other reseller
$adm_res->test_delete(
name => 'delete other reseller admin as su admin',
item => $adm,
expected_result => { code => '204' }
);
# superuser can't delete self
$adm_res->test_delete(
name => 'delete self as su admin',
item => $ref->data('my_super_admin'),
expected_result => { code => '403' }
);
### read-only tests
$adm_res->client($c_ro_admin);
# ro admin can fetch all admins
$admins = $adm_res->test_get(
name => 'fetch all admins as ro admin',
expected_links => [],
expected_result => { code => '200' }
);
my @admins = @{ $admins->[0]->{_embedded}->{'ngcp:admins'} };
foreach my $a(@admins) {
is($a->{reseller_id}, undef,
"ro-fetched admin belongs to own reseller");
$test->inc_test_count();
}
# ro admin can't create admin
$adm_res->test_post(
name => 'create reseller as ro admin',
data_replace => [
{ field => 'login', value => "roadm_$sid" }
],
expected_result => [
{ code => '403' },
],
);
# ro admin can't update own admin
$adm_res->test_put(
name => 'update self as ro admin',
item => $ref->data('my_ro_admin'),
data_replace => {
field => 'login', value => "up_adm_$sid"
},
expected_result => { code => '404' }
);
# ro admin can't update other admin
$adm_res->test_put(
name => 'update self as ro admin',
item => $ref->data('my_reseller_admin'),
data_replace => {
field => 'login', value => "up_adm_$sid"
},
expected_result => { code => '404' }
);
# ro admin can't delete other admin
$adm_res->test_delete(
name => 'delete reseller admin as ro admin',
item => $ref->data('my_reseller_admin'),
expected_result => { code => '403' }
);
# ro admin can't delete self
$adm_res->test_delete(
name => 'delete self as ro admin',
item => $ref->data('my_ro_admin'),
expected_result => { code => '403' }
);
### reseller admin tests
$adm_res->client($c_res_admin);
# res admin can fetch all admins
$admins = $adm_res->test_get(
name => 'fetch all admins as reseller admin',
expected_links => [],
expected_result => { code => '200' }
);
@admins = @{ $admins->[0]->{_embedded}->{'ngcp:admins'} };
foreach my $a(@admins) {
is($a->{reseller_id}, undef,
"reseller-fetched admin belongs to own reseller");
$test->inc_test_count();
}
# reseller admin can create admin
$adm_res->test_post(
name => 'create reseller as reseller admin',
data_replace => [
{ field => 'login', value => "resadm_$sid" }
],
skip_test_fields => [qw/reseller_id password/],
expected_result => [
{ code => '201' },
],
);
$adm = $adm_res->pop_created_item();
# reseller admin can update own admin
$adm_res->test_put(
name => 'update self as reseller admin',
item => $ref->data('my_reseller_admin'),
data_replace => {
field => 'login', value => "up_adm_$sid"
},
expected_result => { code => '404' }
);
# reseller admin can update other admin
$adm_res->test_put(
name => 'update other as reseller admin',
item => $ref->data('my_reseller_admin'),
data_replace => {
field => 'login', value => "up_adm_$sid"
},
expected_result => { code => '404' }
);
# reseller admin can delete other admin
$adm_res->test_delete(
name => 'delete reseller admin as reseller admin',
item => $adm,
expected_result => { code => '204' }
);
# reseller admin can't delete other admin in foreign reseller
$adm_res->test_delete(
name => 'delete other reseller admin as reseller admin',
item => $ref->data('my_ro_admin'),
expected_result => { code => '404' }
);
# reseller admin can't delete self
$adm_res->test_delete(
name => 'delete self as reseller admin',
item => $ref->data('my_reseller_admin'),
expected_result => { code => '403' }
);
$adm_res->test_get(
name => 'fetch other admin as reseller admin',
item => $ref->data('my_std_admin'),
skip_test_fields => [qw/is_superuser lawful_intercept reseller_id/],
expected_result => { code => '200' }
);
$adm_res->test_get(
name => 'fetch other reseller admin as reseller admin',
item => $ref->data('my_ro_admin'),
expected_result => { code => '404' }
);
### standard reseller without master tests
$adm_res->client($c_std_admin);
# std admin can only fetch self
$admins = $adm_res->test_get(
name => 'fetch all admins as reseller',
expected_links => [],
expected_result => { code => '200' }
);
is($admins->[0]->{total_count}, 1, "check if only own admin is returned");
$test->inc_test_count();
@admins = @{ $admins->[0]->{_embedded}->{'ngcp:admins'} };
foreach my $a(@admins) {
is($a->{id}, $ref->data('my_std_admin')->{id},
"std reseller-fetched admin is self");
$test->inc_test_count();
}
# std admin can't create admin
$adm_res->test_post(
name => 'create admin as std admin',
data_replace => [
{ field => 'login', value => "resadm_$sid" }
],
expected_result => [
{ code => '403' },
],
);
# reseller admin can update own admin
$adm_res->test_put(
name => 'update self as std admin',
item => $ref->data('my_std_admin'),
data_replace => {
field => 'login', value => "up_adm_$sid"
},
expected_result => { code => '404' }
);
# reseller admin can update other admin
$adm_res->test_put(
name => 'update other as std admin',
item => $ref->data('my_reseller_admin'),
data_replace => {
field => 'login', value => "up_adm_$sid"
},
expected_result => { code => '404' }
);
# reseller admin can't delete other admin
$adm_res->test_delete(
name => 'delete reseller admin as std admin',
item => $ref->data('my_reseller_admin'),
expected_result => { code => '404' }
);
$adm_res->test_delete(
name => 'delete other reseller admin as std admin',
item => $ref->data('my_ro_admin'),
expected_result => { code => '404' }
);
# reseller admin can't delete self
$adm_res->test_delete(
name => 'delete self as reseller admin',
item => $ref->data('my_std_admin'),
expected_result => { code => '403' }
);
$adm_res->test_get(
name => 'fetch other admin as std admin',
item => $ref->data('my_reseller_admin'),
expected_result => { code => '404' }
);
$adm_res->test_get(
name => 'fetch other reseller admin as std admin',
item => $ref->data('my_ro_admin'),
expected_result => { code => '404' }
);
$test->done();

@ -0,0 +1,417 @@
#!/usr/bin/perl -w
use strict;
use NGCP::Test;
use Test::More;
use Clone 'clone';
use JSON qw/from_json to_json/;
use Data::Dumper;
my $test = NGCP::Test->new(log_debug => 0);
my $t = $test->generate_sid();
my $c_admin = $test->client();
my $ref = $test->reference_data(
client => $c_admin,
use_persistent => 1,
delete_persistent => 0,
depends => [
{
resource => 'admins',
hints => [{ name => 'reseller admin' }],
name => 'my_reseller_admin'
},
{
resource => 'admins',
hints => [{ name => 'rtc reseller admin' }],
name => 'my_rtcreseller_admin'
},
{
resource => 'subscriberprofiles',
hints => [{ name => 'subscriber profile' }],
name => 'my_sub_profile'
},
{
resource => 'subscribers',
hints => [{ name => 'pbx pilot subscriber' }],
name => 'my_pbx_subscriber'
},
],
);
my $sid = $ref->sid();
# test capabilities as admin
diag("test capabilities as admin");
{
my $expected = {
cloudpbx => $ENV{HAS_CLOUDPBX} // 0,
sms => $ENV{HAS_SMS} // 0,
faxserver => $ENV{HAS_FAXSERVER} // 1,
rtcengine => $ENV{HAS_RTCENGINE} // 0,
fileshare => $ENV{HAS_FILESHARE} // 0,
mobilepush => $ENV{HAS_MOBILEPUSH} // 0,
};
my $cap_res = $test->resource(
client => $c_admin,
resource => 'capabilities',
);
my $caps = $cap_res->test_get(
name => 'fetch capabilities as admin',
expected_links => [],
expected_fields => [qw/
id name enabled
/],
expected_result => { code => 200 }
);
my $tmpexpected = clone($expected);
my @caps = ();
foreach my $cap (@{ $caps->[0]->{_embedded}->{'ngcp:capabilities'} }) {
my $name = $cap->{name};
my $val = $cap->{enabled} ? 1 : 0;
is(exists $expected->{$name}, 1, "returned admin capability $name is expected");
$test->inc_test_count();
is($val, $expected->{$name}, "returned admin capability $name has value $$expected{$name}");
$test->inc_test_count();
delete $tmpexpected->{$cap->{name}};
push @caps, $cap;
}
is(keys %{ $tmpexpected }, 0, "all expected admin capabilities seen");
$test->inc_test_count();
foreach my $cap(@caps) {
diag("test individual capability $cap->{name} with id $cap->{id} as admin");
my $items = $cap_res->test_get(
name => "fetch individual capability $cap->{name} as admin",
item => $cap,
expected_links => [],
expected_fields => [qw/
id name enabled
/],
expected_result => { code => 200 }
);
}
}
# test capabilities as reseller without rtcengine enabled
diag("test capabilities as reseller without rtcengine");
{
my $c_reseller = $test->client(
role => 'reseller',
username => $ref->data('my_reseller_admin')->{login},
password => "reseller_$sid",
);
my $expected = {
cloudpbx => $ENV{HAS_CLOUDPBX} // 0,
sms => $ENV{HAS_SMS} // 0,
faxserver => $ENV{HAS_FAXSERVER} // 1,
rtcengine => 0,
fileshare => $ENV{HAS_FILESHARE} // 0,
mobilepush => $ENV{HAS_MOBILEPUSH} // 0,
};
my $cap_res = $test->resource(
client => $c_reseller,
resource => 'capabilities',
);
my $caps = $cap_res->test_get(
name => 'fetch capabilities as reseller',
expected_links => [],
expected_fields => [qw/
id name enabled
/],
expected_result => { code => 200 }
);
my $tmpexpected = clone($expected);
my @caps = ();
foreach my $cap (@{ $caps->[0]->{_embedded}->{'ngcp:capabilities'} }) {
my $name = $cap->{name};
my $val = $cap->{enabled} ? 1 : 0;
is(exists $expected->{$name}, 1, "returned reseller capability $name is expected");
$test->inc_test_count();
is($val, $expected->{$name}, "returned reseller capability $name has value $$expected{$name}");
$test->inc_test_count();
delete $tmpexpected->{$cap->{name}};
push @caps, $cap;
}
is(keys %{ $tmpexpected }, 0, "all expected reseller capabilities seen");
$test->inc_test_count();
foreach my $cap(@caps) {
diag("test individual capability $cap->{name} with id $cap->{id} as reseller");
my $items = $cap_res->test_get(
name => "fetch individual capability $cap->{name} as reseller",
item => $cap,
expected_links => [],
expected_fields => [qw/
id name enabled
/],
expected_result => { code => 200 }
);
}
}
# test capabilities as reseller with rtcengine enabled
diag("test capabilities as reseller with rtcengine");
{
my $c_rtcreseller = $test->client(
role => 'reseller',
username => $ref->data('my_rtcreseller_admin')->{login},
password => "rtcreseller_$sid",
);
my $expected = {
cloudpbx => $ENV{HAS_CLOUDPBX} // 0,
sms => $ENV{HAS_SMS} // 0,
faxserver => $ENV{HAS_FAXSERVER} // 1,
rtcengine => $ENV{HAS_RTCENGINE} // 1,
fileshare => $ENV{HAS_FILESHARE} // 0,
mobilepush => $ENV{HAS_MOBILEPUSH} // 0,
};
my $cap_res = $test->resource(
client => $c_rtcreseller,
resource => 'capabilities',
);
my $caps = $cap_res->test_get(
name => 'fetch capabilities as rtc reseller',
expected_links => [],
expected_fields => [qw/
id name enabled
/],
expected_result => { code => 200 }
);
my $tmpexpected = clone($expected);
my @caps = ();
foreach my $cap (@{ $caps->[0]->{_embedded}->{'ngcp:capabilities'} }) {
my $name = $cap->{name};
my $val = $cap->{enabled} ? 1 : 0;
is(exists $expected->{$name}, 1, "returned rtc reseller capability $name is expected");
$test->inc_test_count();
is($val, $expected->{$name}, "returned rtc reseller capability $name has value $$expected{$name}");
$test->inc_test_count();
delete $tmpexpected->{$cap->{name}};
push @caps, $cap;
}
is(keys %{ $tmpexpected }, 0, "all expected rtc reseller capabilities seen");
$test->inc_test_count();
foreach my $cap(@caps) {
diag("test individual capability $cap->{name} with id $cap->{id} as rtc reseller");
my $items = $cap_res->test_get(
name => "fetch individual capability $cap->{name} as rtc reseller",
item => $cap,
expected_links => [],
expected_fields => [qw/
id name enabled
/],
expected_result => { code => 200 }
);
}
}
diag("test capabilities as pbx subscriber");
{
my $c_subscriber = $test->client(
role => 'subscriber',
username => $ref->data('my_pbx_subscriber')->{webusername} . '@' .
$ref->data('my_pbx_subscriber')->{domain},
password => $ref->data('my_pbx_subscriber')->{webpassword},
);
my $expected = {
cloudpbx => 1,
sms => $ENV{HAS_SMS} // 0,
faxserver => $ENV{HAS_FAXSERVER} // 1,
rtcengine => 0,
fileshare => $ENV{HAS_FILESHARE} // 0,
mobilepush => $ENV{HAS_MOBILEPUSH} // 0,
};
my $cap_res = $test->resource(
client => $c_subscriber,
resource => 'capabilities',
);
my $caps = $cap_res->test_get(
name => 'fetch capabilities as pbx subscriber',
expected_links => [],
expected_fields => [qw/
id name enabled
/],
expected_result => { code => 200 }
);
my $tmpexpected = clone($expected);
my @caps = ();
foreach my $cap (@{ $caps->[0]->{_embedded}->{'ngcp:capabilities'} }) {
my $name = $cap->{name};
my $val = $cap->{enabled} ? 1 : 0;
is(exists $expected->{$name}, 1, "returned pbx capability $name is expected");
$test->inc_test_count();
is($val, $expected->{$name}, "returned pbx capability $name has value $$expected{$name}");
$test->inc_test_count();
delete $tmpexpected->{$cap->{name}};
push @caps, $cap;
}
is(keys %{ $tmpexpected }, 0, "all expected pbx capabilities seen");
$test->inc_test_count();
foreach my $cap(@caps) {
diag("test individual capability $cap->{name} with id $cap->{id} as pbx subscriber");
my $items = $cap_res->test_get(
name => "fetch individual capability $cap->{name} as pbx reseller",
item => $cap,
expected_links => [],
expected_fields => [qw/
id name enabled
/],
expected_result => { code => 200 }
);
}
# get a subscriber profile assigned
my $sub_res = $test->resource(
client => $c_admin,
resource => 'subscribers'
);
$sub_res->test_put(
name => 'assign subscriber profile',
item => $ref->data('my_pbx_subscriber'),
skip_test_fields => [qw/modify_timestamp/],
data_replace => [[
{
field => 'profile_set_id',
value => $ref->data('my_sub_profile')->{profile_set_id},
},
{
field => 'profile_id',
value => $ref->data('my_sub_profile')->{id},
},
]],
expected_result => { code => '200' },
);
$caps = $cap_res->test_get(
name => 'fetch capabilities as pbx subscriber after profile',
expected_links => [],
expected_fields => [qw/
id name enabled
/],
expected_result => { code => 200 }
);
$expected->{faxserver} = $ENV{FAXSERVER}//1;
$tmpexpected = clone($expected);
@caps = ();
foreach my $cap (@{ $caps->[0]->{_embedded}->{'ngcp:capabilities'} }) {
my $name = $cap->{name};
my $val = $cap->{enabled} ? 1 : 0;
is(exists $expected->{$name}, 1, "returned pbx profile capability $name is expected");
$test->inc_test_count();
is($val, $expected->{$name}, "returned pbx profile capability $name has value $$expected{$name}");
$test->inc_test_count();
delete $tmpexpected->{$cap->{name}};
push @caps, $cap;
}
is(keys %{ $tmpexpected }, 0, "all expected pbx profile capabilities seen");
$test->inc_test_count();
foreach my $cap(@caps) {
diag("test individual pbx profile capability $cap->{name} with id $cap->{id} as pbx subscriber");
my $items = $cap_res->test_get(
name => "fetch individual pbx profile capability $cap->{name}",
item => $cap,
expected_links => [],
expected_fields => [qw/
id name enabled
/],
expected_result => { code => 200 }
);
}
# modify profile to remove fax_server attribute
my $prof_res = $test->resource(
client => $c_admin,
resource => 'subscriberprofiles'
);
my $orig_attrs = clone($ref->data('my_sub_profile')->{attributes});
$prof_res->test_put(
name => 'remove all attributes',
item => $ref->data('my_sub_profile'),
data_replace => {
field => 'attributes', value => [qw/ncos clir/],
},
expected_result => { code => '200' },
);
# test again
$caps = $cap_res->test_get(
name => 'fetch capabilities as pbx subscriber after profile change',
expected_links => [],
expected_fields => [qw/
id name enabled
/],
expected_result => { code => 200 }
);
$expected->{faxserver} = 0;
$tmpexpected = clone($expected);
@caps = ();
foreach my $cap (@{ $caps->[0]->{_embedded}->{'ngcp:capabilities'} }) {
my $name = $cap->{name};
my $val = $cap->{enabled} ? 1 : 0;
is(exists $expected->{$name}, 1, "returned pbx profile change capability $name is expected");
$test->inc_test_count();
is($val, $expected->{$name}, "returned pbx profile change capability $name has value $$expected{$name}");
$test->inc_test_count();
delete $tmpexpected->{$cap->{name}};
push @caps, $cap;
}
is(keys %{ $tmpexpected }, 0, "all expected pbx profile change capabilities seen");
$test->inc_test_count();
foreach my $cap(@caps) {
diag("test individual pbx profile change capability $cap->{name} with id $cap->{id} as pbx subscriber");
my $items = $cap_res->test_get(
name => "fetch individual pbx profile change capability $cap->{name}",
item => $cap,
expected_links => [],
expected_fields => [qw/
id name enabled
/],
expected_result => { code => 200 }
);
}
# reset profile
$sub_res->test_put(
name => 'unassign subscriber profile',
item => $ref->data('my_pbx_subscriber'),
skip_test_fields => [qw/modify_timestamp/],
data_replace => [[
{ field => 'profile_set_id', value => undef },
{ field => 'profile_id', value => undef },
]],
expected_result => { code => '200' },
);
$prof_res->test_put(
name => 'restore profile attributes',
item => $ref->data('my_sub_profile'),
data_replace => {
field => 'attributes', value => $orig_attrs,
},
expected_result => { code => '200' },
);
}
$test->done();

@ -55,6 +55,7 @@ $ua = Test::Collection->new()->ua();
callrecordings => 1,
callrecordingstreams => 1,
calls => 1,
capabilities => 1,
ccmapentries => 1,
cfdestinationsets => 1,
cfmappings => 1,

@ -7,6 +7,7 @@ use LWP::UserAgent;
use JSON qw/from_json to_json/;
use IO::Uncompress::Unzip;
use Time::HiRes qw/gettimeofday tv_interval/;
use Digest::MD5 qw/md5_hex/;
use Data::Dumper;
has 'username' => (
@ -149,7 +150,10 @@ has '_crt_path' => (
is => 'ro',
isa => 'Str',
lazy => 1,
default => '/tmp/apicert.pem',
default => sub {
my ($self) = @_;
return '/tmp/' . md5_hex($self->username) . ".crt";
}
);
has 'last_rtt' => (

@ -0,0 +1,47 @@
[
{
"name": "superuser admin",
"type": "admins",
"depends": [],
"data": {
"reseller_id": 1,
"login": "superadmin_${sid}",
"password": "superadmin_${sid}",
"is_superuser": true,
"is_master": true
}
},
{
"name": "readonly admin",
"type": "admins",
"depends": [],
"data": {
"reseller_id": 1,
"login": "roadmin_${sid}",
"password": "roadmin_${sid}",
"read_only": true
}
},
{
"name": "master reseller admin",
"type": "admins",
"depends": [ "reseller" ],
"data": {
"reseller_id": "${reseller}",
"login": "mstreselleradmin_${sid}",
"password": "mstreselleradmin_${sid}",
"is_master": true
}
},
{
"name": "nomaster reseller admin",
"type": "admins",
"depends": [ "reseller" ],
"data": {
"reseller_id": "${reseller}",
"login": "stdreselleradmin_${sid}",
"password": "stdreselleradmin_${sid}",
"is_master": false
}
}
]

@ -13,6 +13,20 @@
"status": "active"
}
},
{
"name": "rtc reseller contract",
"type": "contracts",
"depends": [
"default systemcontact",
"system billingprofile"
],
"data": {
"billing_profile_id": "${system billingprofile}",
"contact_id": "${default systemcontact}",
"type": "reseller",
"status": "active"
}
},
{
"name": "locked reseller contract",
"type": "contracts",

@ -8,7 +8,45 @@
"data": {
"contract_id": "${reseller contract}",
"name": "reseller_${sid}",
"status": "active"
"status": "active",
"enable_rtc": false
}
},
{
"name": "reseller admin",
"type": "admins",
"depends": [
"reseller"
],
"data": {
"reseller_id": "${reseller}",
"login": "reseller_${sid}",
"password": "reseller_${sid}"
}
},
{
"name": "rtc reseller",
"type": "resellers",
"depends": [
"rtc reseller contract"
],
"data": {
"contract_id": "${rtc reseller contract}",
"name": "rtcreseller_${sid}",
"status": "active",
"enable_rtc": true
}
},
{
"name": "rtc reseller admin",
"type": "admins",
"depends": [
"rtc reseller"
],
"data": {
"reseller_id": "${rtc reseller}",
"login": "rtcreseller_${sid}",
"password": "rtcreseller_${sid}"
}
}
]

@ -0,0 +1,32 @@
[
{
"name": "subscriber profile set",
"type": "subscriberprofilesets",
"depends": [
"reseller"
],
"data": {
"reseller_id": "${reseller}",
"name": "subprofileset_${sid}",
"description": "subprofileset ${sid}"
}
},
{
"name": "subscriber profile",
"type": "subscriberprofiles",
"depends": [
"subscriber profile set"
],
"data": {
"profile_set_id": "${subscriber profile set}",
"name": "subscriber_profile_${sid}",
"description": "subscriber profile ${sid}",
"set_default": false,
"attributes": [
"fax_server",
"ncos",
"cfu"
]
}
}
]
Loading…
Cancel
Save