mirror of
https://github.com/cwinfo/powerdns-admin.git
synced 2025-01-27 20:24:39 +00:00
feat: Associate an API Key with accounts (#1044)
This commit is contained in:
parent
6c1dfd2408
commit
f45ff2ce03
123
docs/API.md
123
docs/API.md
@ -1,105 +1,134 @@
|
|||||||
### API Usage
|
### API Usage
|
||||||
|
|
||||||
|
#### Getting started with docker
|
||||||
|
|
||||||
1. Run docker image docker-compose up, go to UI http://localhost:9191, at http://localhost:9191/swagger is swagger API specification
|
1. Run docker image docker-compose up, go to UI http://localhost:9191, at http://localhost:9191/swagger is swagger API specification
|
||||||
2. Click to register user, type e.g. user: admin and password: admin
|
2. Click to register user, type e.g. user: admin and password: admin
|
||||||
3. Login to UI in settings enable allow domain creation for users, now you can create and manage domains with admin account and also ordinary users
|
3. Login to UI in settings enable allow domain creation for users, now you can create and manage domains with admin account and also ordinary users
|
||||||
4. Encode your user and password to base64, in our example we have user admin and password admin so in linux cmd line we type:
|
4. Click on the API Keys menu then click on teh "Add Key" button to add a new Administrator Key
|
||||||
|
5. Keep the base64 encoded apikey somewhere safe as it won't be available in clear anymore
|
||||||
|
|
||||||
```
|
|
||||||
|
#### Accessing the API
|
||||||
|
|
||||||
|
The PDA API consists of two distinct parts:
|
||||||
|
|
||||||
|
- The /powerdnsadmin endpoints manages PDA content (accounts, users, apikeys) and also allow domain creation/deletion
|
||||||
|
- The /server endpoints are proxying queries to the backend PowerDNS instance's API. PDA acts as a proxy managing several API Keys and permissions to the PowerDNS content.
|
||||||
|
|
||||||
|
The requests to the API needs two headers:
|
||||||
|
|
||||||
|
- The classic 'Content-Type: application/json' is required to all POST and PUT requests, though it's armless to use it on each call
|
||||||
|
- The authentication header to provide either the login:password basic authentication or the Api Key authentication.
|
||||||
|
|
||||||
|
When you access the `/powerdnsadmin` endpoint, you must use the Basic Auth:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Encode your user and password to base64
|
||||||
$ echo -n 'admin:admin'|base64
|
$ echo -n 'admin:admin'|base64
|
||||||
YWRtaW46YWRtaW4=
|
YWRtaW46YWRtaW4=
|
||||||
|
# Use the ouput as your basic auth header
|
||||||
|
curl -H 'Authorization: Basic YWRtaW46YWRtaW4=' -X <method> <url>
|
||||||
```
|
```
|
||||||
|
|
||||||
we use generated output in basic authentication, we authenticate as user,
|
When you access the `/server` endpoint, you must use the ApiKey
|
||||||
with basic authentication, we can create/delete/get zone and create/delete/get/update apikeys
|
|
||||||
|
|
||||||
creating domain:
|
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Use the already base64 encoded key in your header
|
||||||
|
curl -H 'X-API-Key: YUdDdGhQM0tMQWV5alpJ' -X <method> <url>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Finally, the `/sync_domains` endpoint accepts both basic and apikey authentication
|
||||||
|
|
||||||
|
#### Examples
|
||||||
|
|
||||||
|
Creating domain via `/powerdnsadmin`:
|
||||||
|
|
||||||
|
```bash
|
||||||
curl -L -vvv -H 'Content-Type: application/json' -H 'Authorization: Basic YWRtaW46YWRtaW4=' -X POST http://localhost:9191/api/v1/pdnsadmin/zones --data '{"name": "yourdomain.com.", "kind": "NATIVE", "nameservers": ["ns1.mydomain.com."]}'
|
curl -L -vvv -H 'Content-Type: application/json' -H 'Authorization: Basic YWRtaW46YWRtaW4=' -X POST http://localhost:9191/api/v1/pdnsadmin/zones --data '{"name": "yourdomain.com.", "kind": "NATIVE", "nameservers": ["ns1.mydomain.com."]}'
|
||||||
```
|
```
|
||||||
|
|
||||||
creating apikey which has Administrator role, apikey can have also User role, when creating such apikey you have to specify also domain for which apikey is valid:
|
Creating an apikey which has the Administrator role:
|
||||||
|
|
||||||
```
|
```bash
|
||||||
|
# Create the key
|
||||||
curl -L -vvv -H 'Content-Type: application/json' -H 'Authorization: Basic YWRtaW46YWRtaW4=' -X POST http://localhost:9191/api/v1/pdnsadmin/apikeys --data '{"description": "masterkey","domains":[], "role": "Administrator"}'
|
curl -L -vvv -H 'Content-Type: application/json' -H 'Authorization: Basic YWRtaW46YWRtaW4=' -X POST http://localhost:9191/api/v1/pdnsadmin/apikeys --data '{"description": "masterkey","domains":[], "role": "Administrator"}'
|
||||||
```
|
```
|
||||||
|
Example response (don't forget to save the plain key from the output)
|
||||||
|
|
||||||
call above will return response like this:
|
```json
|
||||||
|
[
|
||||||
```
|
{
|
||||||
[{"description": "samekey", "domains": [], "role": {"name": "Administrator", "id": 1}, "id": 2, "plain_key": "aGCthP3KLAeyjZI"}]
|
"accounts": [],
|
||||||
|
"description": "masterkey",
|
||||||
|
"domains": [],
|
||||||
|
"role": {
|
||||||
|
"name": "Administrator",
|
||||||
|
"id": 1
|
||||||
|
},
|
||||||
|
"id": 2,
|
||||||
|
"plain_key": "aGCthP3KLAeyjZI"
|
||||||
|
}
|
||||||
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
we take plain_key and base64 encode it, this is the only time we can get API key in plain text and save it somewhere:
|
We can use the apikey for all calls to PowerDNS (don't forget to specify Content-Type):
|
||||||
|
|
||||||
```
|
Getting powerdns configuration (Administrator Key is needed):
|
||||||
$ echo -n 'aGCthP3KLAeyjZI'|base64
|
|
||||||
YUdDdGhQM0tMQWV5alpJ
|
|
||||||
```
|
|
||||||
|
|
||||||
We can use apikey for all calls specified in our API specification (it tries to follow powerdns API 1:1, only tsigkeys endpoints are not yet implemented), don't forget to specify Content-Type!
|
```bash
|
||||||
|
|
||||||
getting powerdns configuration:
|
|
||||||
|
|
||||||
```
|
|
||||||
curl -L -vvv -H 'Content-Type: application/json' -H 'X-API-KEY: YUdDdGhQM0tMQWV5alpJ' -X GET http://localhost:9191/api/v1/servers/localhost/config
|
curl -L -vvv -H 'Content-Type: application/json' -H 'X-API-KEY: YUdDdGhQM0tMQWV5alpJ' -X GET http://localhost:9191/api/v1/servers/localhost/config
|
||||||
```
|
```
|
||||||
|
|
||||||
creating and updating records:
|
Creating and updating records:
|
||||||
|
|
||||||
```
|
```bash
|
||||||
curl -X PATCH -H 'Content-Type: application/json' --data '{"rrsets": [{"name": "test1.yourdomain.com.","type": "A","ttl": 86400,"changetype": "REPLACE","records": [ {"content": "192.0.2.5", "disabled": false} ]},{"name": "test2.yourdomain.com.","type": "AAAA","ttl": 86400,"changetype": "REPLACE","records": [ {"content": "2001:db8::6", "disabled": false} ]}]}' -H 'X-API-Key: YUdDdGhQM0tMQWV5alpJ' http://127.0.0.1:9191/api/v1/servers/localhost/zones/yourdomain.com.
|
curl -X PATCH -H 'Content-Type: application/json' --data '{"rrsets": [{"name": "test1.yourdomain.com.","type": "A","ttl": 86400,"changetype": "REPLACE","records": [ {"content": "192.0.2.5", "disabled": false} ]},{"name": "test2.yourdomain.com.","type": "AAAA","ttl": 86400,"changetype": "REPLACE","records": [ {"content": "2001:db8::6", "disabled": false} ]}]}' -H 'X-API-Key: YUdDdGhQM0tMQWV5alpJ' http://127.0.0.1:9191/api/v1/servers/localhost/zones/yourdomain.com.
|
||||||
```
|
```
|
||||||
|
|
||||||
getting domain:
|
Getting a domain:
|
||||||
|
|
||||||
```
|
```bash
|
||||||
curl -L -vvv -H 'Content-Type: application/json' -H 'X-API-KEY: YUdDdGhQM0tMQWV5alpJ' -X GET http://localhost:9191/api/v1/servers/localhost/zones/yourdomain.com
|
curl -L -vvv -H 'Content-Type: application/json' -H 'X-API-KEY: YUdDdGhQM0tMQWV5alpJ' -X GET http://localhost:9191/api/v1/servers/localhost/zones/yourdomain.com
|
||||||
```
|
```
|
||||||
|
|
||||||
list zone records:
|
List a zone's records:
|
||||||
|
|
||||||
```
|
```bash
|
||||||
curl -H 'Content-Type: application/json' -H 'X-API-Key: YUdDdGhQM0tMQWV5alpJ' http://localhost:9191/api/v1/servers/localhost/zones/yourdomain.com
|
curl -H 'Content-Type: application/json' -H 'X-API-Key: YUdDdGhQM0tMQWV5alpJ' http://localhost:9191/api/v1/servers/localhost/zones/yourdomain.com
|
||||||
```
|
```
|
||||||
|
|
||||||
add new record:
|
Add a new record:
|
||||||
|
|
||||||
```
|
```bash
|
||||||
curl -H 'Content-Type: application/json' -X PATCH --data '{"rrsets": [ {"name": "test.yourdomain.com.", "type": "A", "ttl": 86400, "changetype": "REPLACE", "records": [ {"content": "192.0.5.4", "disabled": false } ] } ] }' -H 'X-API-Key: YUdDdGhQM0tMQWV5alpJ' http://localhost:9191/api/v1/servers/localhost/zones/yourdomain.com | jq .
|
curl -H 'Content-Type: application/json' -X PATCH --data '{"rrsets": [ {"name": "test.yourdomain.com.", "type": "A", "ttl": 86400, "changetype": "REPLACE", "records": [ {"content": "192.0.5.4", "disabled": false } ] } ] }' -H 'X-API-Key: YUdDdGhQM0tMQWV5alpJ' http://localhost:9191/api/v1/servers/localhost/zones/yourdomain.com | jq .
|
||||||
```
|
```
|
||||||
|
|
||||||
update record:
|
Update a record:
|
||||||
|
|
||||||
```
|
```bash
|
||||||
curl -H 'Content-Type: application/json' -X PATCH --data '{"rrsets": [ {"name": "test.yourdomain.com.", "type": "A", "ttl": 86400, "changetype": "REPLACE", "records": [ {"content": "192.0.2.5", "disabled": false, "name": "test.yourdomain.com.", "ttl": 86400, "type": "A"}]}]}' -H 'X-API-Key: YUdDdGhQM0tMQWV5alpJ' http://localhost:9191/api/v1/servers/localhost/zones/yourdomain.com | jq .
|
curl -H 'Content-Type: application/json' -X PATCH --data '{"rrsets": [ {"name": "test.yourdomain.com.", "type": "A", "ttl": 86400, "changetype": "REPLACE", "records": [ {"content": "192.0.2.5", "disabled": false, "name": "test.yourdomain.com.", "ttl": 86400, "type": "A"}]}]}' -H 'X-API-Key: YUdDdGhQM0tMQWV5alpJ' http://localhost:9191/api/v1/servers/localhost/zones/yourdomain.com | jq .
|
||||||
```
|
```
|
||||||
|
|
||||||
delete record:
|
Delete a record:
|
||||||
|
|
||||||
```
|
```bash
|
||||||
curl -H 'Content-Type: application/json' -X PATCH --data '{"rrsets": [ {"name": "test.yourdomain.com.", "type": "A", "ttl": 86400, "changetype": "DELETE"}]}' -H 'X-API-Key: YUdDdGhQM0tMQWV5alpJ' http://localhost:9191/api/v1/servers/localhost/zones/yourdomain.com | jq
|
curl -H 'Content-Type: application/json' -X PATCH --data '{"rrsets": [ {"name": "test.yourdomain.com.", "type": "A", "ttl": 86400, "changetype": "DELETE"}]}' -H 'X-API-Key: YUdDdGhQM0tMQWV5alpJ' http://localhost:9191/api/v1/servers/localhost/zones/yourdomain.com | jq
|
||||||
```
|
```
|
||||||
|
|
||||||
### Generate ER diagram
|
### Generate ER diagram
|
||||||
|
|
||||||
```
|
With docker
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install build packages
|
||||||
apt-get install python-dev graphviz libgraphviz-dev pkg-config
|
apt-get install python-dev graphviz libgraphviz-dev pkg-config
|
||||||
```
|
# Get the required python libraries
|
||||||
|
|
||||||
```
|
|
||||||
pip install graphviz mysqlclient ERAlchemy
|
pip install graphviz mysqlclient ERAlchemy
|
||||||
```
|
# Start the docker container
|
||||||
|
|
||||||
```
|
|
||||||
docker-compose up -d
|
docker-compose up -d
|
||||||
```
|
# Set environment variables
|
||||||
|
|
||||||
```
|
|
||||||
source .env
|
source .env
|
||||||
```
|
# Generate the diagrams
|
||||||
|
|
||||||
```
|
|
||||||
eralchemy -i 'mysql://${PDA_DB_USER}:${PDA_DB_PASSWORD}@'$(docker inspect powerdns-admin-mysql|jq -jr '.[0].NetworkSettings.Networks.powerdnsadmin_default.IPAddress')':3306/powerdns_admin' -o /tmp/output.pdf
|
eralchemy -i 'mysql://${PDA_DB_USER}:${PDA_DB_PASSWORD}@'$(docker inspect powerdns-admin-mysql|jq -jr '.[0].NetworkSettings.Networks.powerdnsadmin_default.IPAddress')':3306/powerdns_admin' -o /tmp/output.pdf
|
||||||
```
|
```
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
"""add apikey account mapping table
|
||||||
|
|
||||||
|
Revision ID: 0967658d9c0d
|
||||||
|
Revises: 0d3d93f1c2e0
|
||||||
|
Create Date: 2021-11-13 22:28:46.133474
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '0967658d9c0d'
|
||||||
|
down_revision = '0d3d93f1c2e0'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table('apikey_account',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('apikey_id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('account_id', sa.Integer(), nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(['account_id'], ['account.id'], ),
|
||||||
|
sa.ForeignKeyConstraint(['apikey_id'], ['apikey.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
with op.batch_alter_table('history', schema=None) as batch_op:
|
||||||
|
batch_op.create_index(batch_op.f('ix_history_created_on'), ['created_on'], unique=False)
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
with op.batch_alter_table('history', schema=None) as batch_op:
|
||||||
|
batch_op.drop_index(batch_op.f('ix_history_created_on'))
|
||||||
|
|
||||||
|
op.drop_table('apikey_account')
|
||||||
|
# ### end Alembic commands ###
|
@ -270,7 +270,12 @@ def apikey_can_access_domain(f):
|
|||||||
zone_id = kwargs.get('zone_id').rstrip(".")
|
zone_id = kwargs.get('zone_id').rstrip(".")
|
||||||
domain_names = [item.name for item in domains]
|
domain_names = [item.name for item in domains]
|
||||||
|
|
||||||
if zone_id not in domain_names:
|
accounts = apikey.accounts
|
||||||
|
accounts_domains = [domain.name for a in accounts for domain in a.domains]
|
||||||
|
|
||||||
|
allowed_domains = set(domain_names + accounts_domains)
|
||||||
|
|
||||||
|
if zone_id not in allowed_domains:
|
||||||
raise DomainAccessForbidden()
|
raise DomainAccessForbidden()
|
||||||
return f(*args, **kwargs)
|
return f(*args, **kwargs)
|
||||||
|
|
||||||
|
@ -60,7 +60,8 @@ class ApiKeyNotUsable(StructuredException):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
name=None,
|
name=None,
|
||||||
message="Api key must have domains or have administrative role"):
|
message=("Api key must have domains or accounts"
|
||||||
|
" or an administrative role")):
|
||||||
StructuredException.__init__(self)
|
StructuredException.__init__(self)
|
||||||
self.message = message
|
self.message = message
|
||||||
self.name = name
|
self.name = name
|
||||||
@ -120,6 +121,15 @@ class AccountDeleteFail(StructuredException):
|
|||||||
self.name = name
|
self.name = name
|
||||||
|
|
||||||
|
|
||||||
|
class AccountNotExists(StructuredException):
|
||||||
|
status_code = 404
|
||||||
|
|
||||||
|
def __init__(self, name=None, message="Account does not exist"):
|
||||||
|
StructuredException.__init__(self)
|
||||||
|
self.message = message
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
|
||||||
class UserCreateFail(StructuredException):
|
class UserCreateFail(StructuredException):
|
||||||
status_code = 500
|
status_code = 500
|
||||||
|
|
||||||
|
@ -11,10 +11,21 @@ class RoleSchema(Schema):
|
|||||||
name = fields.String()
|
name = fields.String()
|
||||||
|
|
||||||
|
|
||||||
|
class AccountSummarySchema(Schema):
|
||||||
|
id = fields.Integer()
|
||||||
|
name = fields.String()
|
||||||
|
domains = fields.Embed(schema=DomainSchema, many=True)
|
||||||
|
|
||||||
|
class ApiKeySummarySchema(Schema):
|
||||||
|
id = fields.Integer()
|
||||||
|
description = fields.String()
|
||||||
|
|
||||||
|
|
||||||
class ApiKeySchema(Schema):
|
class ApiKeySchema(Schema):
|
||||||
id = fields.Integer()
|
id = fields.Integer()
|
||||||
role = fields.Embed(schema=RoleSchema)
|
role = fields.Embed(schema=RoleSchema)
|
||||||
domains = fields.Embed(schema=DomainSchema, many=True)
|
domains = fields.Embed(schema=DomainSchema, many=True)
|
||||||
|
accounts = fields.Embed(schema=AccountSummarySchema, many=True)
|
||||||
description = fields.String()
|
description = fields.String()
|
||||||
key = fields.String()
|
key = fields.String()
|
||||||
|
|
||||||
@ -23,15 +34,11 @@ class ApiPlainKeySchema(Schema):
|
|||||||
id = fields.Integer()
|
id = fields.Integer()
|
||||||
role = fields.Embed(schema=RoleSchema)
|
role = fields.Embed(schema=RoleSchema)
|
||||||
domains = fields.Embed(schema=DomainSchema, many=True)
|
domains = fields.Embed(schema=DomainSchema, many=True)
|
||||||
|
accounts = fields.Embed(schema=AccountSummarySchema, many=True)
|
||||||
description = fields.String()
|
description = fields.String()
|
||||||
plain_key = fields.String()
|
plain_key = fields.String()
|
||||||
|
|
||||||
|
|
||||||
class AccountSummarySchema(Schema):
|
|
||||||
id = fields.Integer()
|
|
||||||
name = fields.String()
|
|
||||||
|
|
||||||
|
|
||||||
class UserSchema(Schema):
|
class UserSchema(Schema):
|
||||||
id = fields.Integer()
|
id = fields.Integer()
|
||||||
username = fields.String()
|
username = fields.String()
|
||||||
@ -56,3 +63,4 @@ class AccountSchema(Schema):
|
|||||||
contact = fields.String()
|
contact = fields.String()
|
||||||
mail = fields.String()
|
mail = fields.String()
|
||||||
domains = fields.Embed(schema=DomainSchema, many=True)
|
domains = fields.Embed(schema=DomainSchema, many=True)
|
||||||
|
apikeys = fields.Embed(schema=ApiKeySummarySchema, many=True)
|
||||||
|
@ -8,6 +8,7 @@ from .account_user import AccountUser
|
|||||||
from .server import Server
|
from .server import Server
|
||||||
from .history import History
|
from .history import History
|
||||||
from .api_key import ApiKey
|
from .api_key import ApiKey
|
||||||
|
from .api_key_account import ApiKeyAccount
|
||||||
from .setting import Setting
|
from .setting import Setting
|
||||||
from .domain import Domain
|
from .domain import Domain
|
||||||
from .domain_setting import DomainSetting
|
from .domain_setting import DomainSetting
|
||||||
|
@ -17,6 +17,9 @@ class Account(db.Model):
|
|||||||
contact = db.Column(db.String(128))
|
contact = db.Column(db.String(128))
|
||||||
mail = db.Column(db.String(128))
|
mail = db.Column(db.String(128))
|
||||||
domains = db.relationship("Domain", back_populates="account")
|
domains = db.relationship("Domain", back_populates="account")
|
||||||
|
apikeys = db.relationship("ApiKey",
|
||||||
|
secondary="apikey_account",
|
||||||
|
back_populates="accounts")
|
||||||
|
|
||||||
def __init__(self, name=None, description=None, contact=None, mail=None):
|
def __init__(self, name=None, description=None, contact=None, mail=None):
|
||||||
self.name = name
|
self.name = name
|
||||||
|
@ -3,10 +3,10 @@ import string
|
|||||||
import bcrypt
|
import bcrypt
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
|
|
||||||
from .base import db, domain_apikey
|
from .base import db
|
||||||
from ..models.role import Role
|
from ..models.role import Role
|
||||||
from ..models.domain import Domain
|
from ..models.domain import Domain
|
||||||
|
from ..models.account import Account
|
||||||
|
|
||||||
class ApiKey(db.Model):
|
class ApiKey(db.Model):
|
||||||
__tablename__ = "apikey"
|
__tablename__ = "apikey"
|
||||||
@ -16,14 +16,18 @@ class ApiKey(db.Model):
|
|||||||
role_id = db.Column(db.Integer, db.ForeignKey('role.id'))
|
role_id = db.Column(db.Integer, db.ForeignKey('role.id'))
|
||||||
role = db.relationship('Role', back_populates="apikeys", lazy=True)
|
role = db.relationship('Role', back_populates="apikeys", lazy=True)
|
||||||
domains = db.relationship("Domain",
|
domains = db.relationship("Domain",
|
||||||
secondary=domain_apikey,
|
secondary="domain_apikey",
|
||||||
back_populates="apikeys")
|
back_populates="apikeys")
|
||||||
|
accounts = db.relationship("Account",
|
||||||
|
secondary="apikey_account",
|
||||||
|
back_populates="apikeys")
|
||||||
|
|
||||||
def __init__(self, key=None, desc=None, role_name=None, domains=[]):
|
def __init__(self, key=None, desc=None, role_name=None, domains=[], accounts=[]):
|
||||||
self.id = None
|
self.id = None
|
||||||
self.description = desc
|
self.description = desc
|
||||||
self.role_name = role_name
|
self.role_name = role_name
|
||||||
self.domains[:] = domains
|
self.domains[:] = domains
|
||||||
|
self.accounts[:] = accounts
|
||||||
if not key:
|
if not key:
|
||||||
rand_key = ''.join(
|
rand_key = ''.join(
|
||||||
random.choice(string.ascii_letters + string.digits)
|
random.choice(string.ascii_letters + string.digits)
|
||||||
@ -54,7 +58,7 @@ class ApiKey(db.Model):
|
|||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
def update(self, role_name=None, description=None, domains=None):
|
def update(self, role_name=None, description=None, domains=None, accounts=None):
|
||||||
try:
|
try:
|
||||||
if role_name:
|
if role_name:
|
||||||
role = Role.query.filter(Role.name == role_name).first()
|
role = Role.query.filter(Role.name == role_name).first()
|
||||||
@ -63,12 +67,18 @@ class ApiKey(db.Model):
|
|||||||
if description:
|
if description:
|
||||||
self.description = description
|
self.description = description
|
||||||
|
|
||||||
if domains:
|
if domains is not None:
|
||||||
domain_object_list = Domain.query \
|
domain_object_list = Domain.query \
|
||||||
.filter(Domain.name.in_(domains)) \
|
.filter(Domain.name.in_(domains)) \
|
||||||
.all()
|
.all()
|
||||||
self.domains[:] = domain_object_list
|
self.domains[:] = domain_object_list
|
||||||
|
|
||||||
|
if accounts is not None:
|
||||||
|
account_object_list = Account.query \
|
||||||
|
.filter(Account.name.in_(accounts)) \
|
||||||
|
.all()
|
||||||
|
self.accounts[:] = account_object_list
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
msg_str = 'Update of apikey failed. Error: {0}'
|
msg_str = 'Update of apikey failed. Error: {0}'
|
||||||
@ -121,3 +131,12 @@ class ApiKey(db.Model):
|
|||||||
raise Exception("Unauthorized")
|
raise Exception("Unauthorized")
|
||||||
|
|
||||||
return apikey
|
return apikey
|
||||||
|
|
||||||
|
def associate_account(self, account):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def dissociate_account(self, account):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_accounts(self):
|
||||||
|
return True
|
||||||
|
20
powerdnsadmin/models/api_key_account.py
Normal file
20
powerdnsadmin/models/api_key_account.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
from .base import db
|
||||||
|
|
||||||
|
|
||||||
|
class ApiKeyAccount(db.Model):
|
||||||
|
__tablename__ = 'apikey_account'
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
apikey_id = db.Column(db.Integer,
|
||||||
|
db.ForeignKey('apikey.id'),
|
||||||
|
nullable=False)
|
||||||
|
account_id = db.Column(db.Integer,
|
||||||
|
db.ForeignKey('account.id'),
|
||||||
|
nullable=False)
|
||||||
|
db.UniqueConstraint('apikey_id', 'account_id', name='uniq_apikey_account')
|
||||||
|
|
||||||
|
def __init__(self, apikey_id, account_id):
|
||||||
|
self.apikey_id = apikey_id
|
||||||
|
self.account_id = account_id
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<ApiKey_Account {0} {1}>'.format(self.apikey_id, self.account_id)
|
@ -40,7 +40,7 @@ old_state: dictionary with "disabled" and "content" keys. {"disabled" : False, "
|
|||||||
new_state: similarly
|
new_state: similarly
|
||||||
change_type: "addition" or "deletion" or "status" for status change or "unchanged" for no change
|
change_type: "addition" or "deletion" or "status" for status change or "unchanged" for no change
|
||||||
|
|
||||||
Note: A change in "content", is considered a deletion and recreation of the same record,
|
Note: A change in "content", is considered a deletion and recreation of the same record,
|
||||||
holding the new content value.
|
holding the new content value.
|
||||||
"""
|
"""
|
||||||
def get_record_changes(del_rrest, add_rrest):
|
def get_record_changes(del_rrest, add_rrest):
|
||||||
@ -57,12 +57,12 @@ def get_record_changes(del_rrest, add_rrest):
|
|||||||
{"disabled":a['disabled'],"content":a['content']},
|
{"disabled":a['disabled'],"content":a['content']},
|
||||||
"status") )
|
"status") )
|
||||||
break
|
break
|
||||||
|
|
||||||
if not exists: # deletion
|
if not exists: # deletion
|
||||||
changeSet.append( ({"disabled":d['disabled'],"content":d['content']},
|
changeSet.append( ({"disabled":d['disabled'],"content":d['content']},
|
||||||
None,
|
None,
|
||||||
"deletion") )
|
"deletion") )
|
||||||
|
|
||||||
for a in addSet: # get the additions
|
for a in addSet: # get the additions
|
||||||
exists = False
|
exists = False
|
||||||
for d in delSet:
|
for d in delSet:
|
||||||
@ -78,7 +78,7 @@ def get_record_changes(del_rrest, add_rrest):
|
|||||||
exists = False
|
exists = False
|
||||||
for c in changeSet:
|
for c in changeSet:
|
||||||
if c[1] != None and c[1]["content"] == a['content']:
|
if c[1] != None and c[1]["content"] == a['content']:
|
||||||
exists = True
|
exists = True
|
||||||
break
|
break
|
||||||
if not exists:
|
if not exists:
|
||||||
changeSet.append( ( {"disabled":a['disabled'], "content":a['content']}, {"disabled":a['disabled'], "content":a['content']}, "unchanged") )
|
changeSet.append( ( {"disabled":a['disabled'], "content":a['content']}, {"disabled":a['disabled'], "content":a['content']}, "unchanged") )
|
||||||
@ -123,7 +123,7 @@ def extract_changelogs_from_a_history_entry(out_changes, history_entry, change_n
|
|||||||
if change_num not in out_changes:
|
if change_num not in out_changes:
|
||||||
out_changes[change_num] = []
|
out_changes[change_num] = []
|
||||||
out_changes[change_num].append(HistoryRecordEntry(history_entry, del_rrest, [], "-"))
|
out_changes[change_num].append(HistoryRecordEntry(history_entry, del_rrest, [], "-"))
|
||||||
|
|
||||||
|
|
||||||
# only used for changelog per record
|
# only used for changelog per record
|
||||||
if record_name != None and record_type != None: # then get only the records with the specific (record_name, record_type) tuple
|
if record_name != None and record_type != None: # then get only the records with the specific (record_name, record_type) tuple
|
||||||
@ -172,7 +172,7 @@ class HistoryRecordEntry:
|
|||||||
if add_rrest['ttl'] != del_rrest['ttl']:
|
if add_rrest['ttl'] != del_rrest['ttl']:
|
||||||
self.changed_fields.append("ttl")
|
self.changed_fields.append("ttl")
|
||||||
self.changeSet = get_record_changes(del_rrest, add_rrest)
|
self.changeSet = get_record_changes(del_rrest, add_rrest)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def toDict(self):
|
def toDict(self):
|
||||||
@ -300,6 +300,7 @@ def edit_user(user_username=None):
|
|||||||
@operator_role_required
|
@operator_role_required
|
||||||
def edit_key(key_id=None):
|
def edit_key(key_id=None):
|
||||||
domains = Domain.query.all()
|
domains = Domain.query.all()
|
||||||
|
accounts = Account.query.all()
|
||||||
roles = Role.query.all()
|
roles = Role.query.all()
|
||||||
apikey = None
|
apikey = None
|
||||||
create = True
|
create = True
|
||||||
@ -316,6 +317,7 @@ def edit_key(key_id=None):
|
|||||||
return render_template('admin_edit_key.html',
|
return render_template('admin_edit_key.html',
|
||||||
key=apikey,
|
key=apikey,
|
||||||
domains=domains,
|
domains=domains,
|
||||||
|
accounts=accounts,
|
||||||
roles=roles,
|
roles=roles,
|
||||||
create=create)
|
create=create)
|
||||||
|
|
||||||
@ -323,14 +325,21 @@ def edit_key(key_id=None):
|
|||||||
fdata = request.form
|
fdata = request.form
|
||||||
description = fdata['description']
|
description = fdata['description']
|
||||||
role = fdata.getlist('key_role')[0]
|
role = fdata.getlist('key_role')[0]
|
||||||
doamin_list = fdata.getlist('key_multi_domain')
|
domain_list = fdata.getlist('key_multi_domain')
|
||||||
|
account_list = fdata.getlist('key_multi_account')
|
||||||
|
|
||||||
# Create new apikey
|
# Create new apikey
|
||||||
if create:
|
if create:
|
||||||
domain_obj_list = Domain.query.filter(Domain.name.in_(doamin_list)).all()
|
if role == "User":
|
||||||
|
domain_obj_list = Domain.query.filter(Domain.name.in_(domain_list)).all()
|
||||||
|
account_obj_list = Account.query.filter(Account.name.in_(account_list)).all()
|
||||||
|
else:
|
||||||
|
account_obj_list, domain_obj_list = [], []
|
||||||
|
|
||||||
apikey = ApiKey(desc=description,
|
apikey = ApiKey(desc=description,
|
||||||
role_name=role,
|
role_name=role,
|
||||||
domains=domain_obj_list)
|
domains=domain_obj_list,
|
||||||
|
accounts=account_obj_list)
|
||||||
try:
|
try:
|
||||||
apikey.create()
|
apikey.create()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -344,7 +353,9 @@ def edit_key(key_id=None):
|
|||||||
# Update existing apikey
|
# Update existing apikey
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
apikey.update(role,description,doamin_list)
|
if role != "User":
|
||||||
|
domain_list, account_list = [], []
|
||||||
|
apikey.update(role,description,domain_list, account_list)
|
||||||
history_message = "Updated API key {0}".format(apikey.id)
|
history_message = "Updated API key {0}".format(apikey.id)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.error('Error: {0}'.format(e))
|
current_app.logger.error('Error: {0}'.format(e))
|
||||||
@ -354,14 +365,16 @@ def edit_key(key_id=None):
|
|||||||
'key': apikey.id,
|
'key': apikey.id,
|
||||||
'role': apikey.role.name,
|
'role': apikey.role.name,
|
||||||
'description': apikey.description,
|
'description': apikey.description,
|
||||||
'domain_acl': [domain.name for domain in apikey.domains]
|
'domains': [domain.name for domain in apikey.domains],
|
||||||
|
'accounts': [a.name for a in apikey.accounts]
|
||||||
}),
|
}),
|
||||||
created_by=current_user.username)
|
created_by=current_user.username)
|
||||||
history.add()
|
history.add()
|
||||||
|
|
||||||
return render_template('admin_edit_key.html',
|
return render_template('admin_edit_key.html',
|
||||||
key=apikey,
|
key=apikey,
|
||||||
domains=domains,
|
domains=domains,
|
||||||
|
accounts=accounts,
|
||||||
roles=roles,
|
roles=roles,
|
||||||
create=create,
|
create=create,
|
||||||
plain_key=plain_key)
|
plain_key=plain_key)
|
||||||
@ -390,7 +403,7 @@ def manage_keys():
|
|||||||
history_apikey_role = apikey.role.name
|
history_apikey_role = apikey.role.name
|
||||||
history_apikey_description = apikey.description
|
history_apikey_description = apikey.description
|
||||||
history_apikey_domains = [ domain.name for domain in apikey.domains]
|
history_apikey_domains = [ domain.name for domain in apikey.domains]
|
||||||
|
|
||||||
apikey.delete()
|
apikey.delete()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.error('Error: {0}'.format(e))
|
current_app.logger.error('Error: {0}'.format(e))
|
||||||
@ -744,7 +757,7 @@ class DetailedHistory():
|
|||||||
self.history = history
|
self.history = history
|
||||||
self.detailed_msg = ""
|
self.detailed_msg = ""
|
||||||
self.change_set = change_set
|
self.change_set = change_set
|
||||||
|
|
||||||
if history.detail is None:
|
if history.detail is None:
|
||||||
self.detailed_msg = ""
|
self.detailed_msg = ""
|
||||||
# if 'Create account' in history.msg:
|
# if 'Create account' in history.msg:
|
||||||
@ -758,16 +771,16 @@ class DetailedHistory():
|
|||||||
if 'domain_type' in detail_dict.keys() and 'account_id' in detail_dict.keys(): # this is a domain creation
|
if 'domain_type' in detail_dict.keys() and 'account_id' in detail_dict.keys(): # this is a domain creation
|
||||||
self.detailed_msg = """
|
self.detailed_msg = """
|
||||||
<table class="table table-bordered table-striped"><tr><td>Domain type:</td><td>{0}</td></tr> <tr><td>Account:</td><td>{1}</td></tr></table>
|
<table class="table table-bordered table-striped"><tr><td>Domain type:</td><td>{0}</td></tr> <tr><td>Account:</td><td>{1}</td></tr></table>
|
||||||
""".format(detail_dict['domain_type'],
|
""".format(detail_dict['domain_type'],
|
||||||
Account.get_name_by_id(self=None, account_id=detail_dict['account_id']) if detail_dict['account_id'] != "0" else "None")
|
Account.get_name_by_id(self=None, account_id=detail_dict['account_id']) if detail_dict['account_id'] != "0" else "None")
|
||||||
elif 'authenticator' in detail_dict.keys(): # this is a user authentication
|
elif 'authenticator' in detail_dict.keys(): # this is a user authentication
|
||||||
self.detailed_msg = """
|
self.detailed_msg = """
|
||||||
<table class="table table-bordered table-striped" style="width:565px;">
|
<table class="table table-bordered table-striped" style="width:565px;">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th colspan="3" style="background:
|
<th colspan="3" style="background:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Change table header background colour depending on auth success or failure
|
# Change table header background colour depending on auth success or failure
|
||||||
if detail_dict['success'] == 1:
|
if detail_dict['success'] == 1:
|
||||||
self.detailed_msg+= """
|
self.detailed_msg+= """
|
||||||
@ -785,7 +798,7 @@ class DetailedHistory():
|
|||||||
|
|
||||||
self.detailed_msg+= """
|
self.detailed_msg+= """
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Authenticator Type:</td>
|
<td>Authenticator Type:</td>
|
||||||
@ -812,18 +825,23 @@ class DetailedHistory():
|
|||||||
<table class="table table-bordered table-striped"><tr><td>Users with access to this domain</td><td>{0}</td></tr><tr><td>Number of users:</td><td>{1}</td><tr></table>
|
<table class="table table-bordered table-striped"><tr><td>Users with access to this domain</td><td>{0}</td></tr><tr><td>Number of users:</td><td>{1}</td><tr></table>
|
||||||
""".format(str(detail_dict['user_has_access']).replace("]","").replace("[", ""), len((detail_dict['user_has_access'])))
|
""".format(str(detail_dict['user_has_access']).replace("]","").replace("[", ""), len((detail_dict['user_has_access'])))
|
||||||
elif 'Created API key' in history.msg or 'Updated API key' in history.msg:
|
elif 'Created API key' in history.msg or 'Updated API key' in history.msg:
|
||||||
|
domains = detail_dict['domains' if 'domains' in detail_dict.keys() else 'domain_acl']
|
||||||
|
accounts = detail_dict['accounts'] if 'accounts' in detail_dict.keys() else 'None'
|
||||||
self.detailed_msg = """
|
self.detailed_msg = """
|
||||||
<table class="table table-bordered table-striped">
|
<table class="table table-bordered table-striped">
|
||||||
<tr><td>Key: </td><td>{0}</td></tr>
|
<tr><td>Key: </td><td>{0}</td></tr>
|
||||||
<tr><td>Role:</td><td>{1}</td></tr>
|
<tr><td>Role:</td><td>{1}</td></tr>
|
||||||
<tr><td>Description:</td><td>{2}</td></tr>
|
<tr><td>Description:</td><td>{2}</td></tr>
|
||||||
<tr><td>Accessible domains with this API key:</td><td>{3}</td></tr>
|
<tr><td>Accounts bound to this API key:</td><td>{3}</td></tr>
|
||||||
|
<tr><td>Accessible domains with this API key:</td><td>{4}</td></tr>
|
||||||
</table>
|
</table>
|
||||||
""".format(detail_dict['key'], detail_dict['role'], detail_dict['description'], str(detail_dict['domain_acl']).replace("]","").replace("[", ""))
|
""".format(detail_dict['key'], detail_dict['role'], detail_dict['description'],
|
||||||
|
str(accounts).replace("]","").replace("[", ""),
|
||||||
|
str(domains).replace("]","").replace("[", ""))
|
||||||
elif 'Update type for domain' in history.msg:
|
elif 'Update type for domain' in history.msg:
|
||||||
self.detailed_msg = """
|
self.detailed_msg = """
|
||||||
<table class="table table-bordered table-striped">
|
<table class="table table-bordered table-striped">
|
||||||
<tr><td>Domain: </td><td>{0}</td></tr>
|
<tr><td>Domain: </td><td>{0}</td></tr>
|
||||||
<tr><td>Domain type:</td><td>{1}</td></tr>
|
<tr><td>Domain type:</td><td>{1}</td></tr>
|
||||||
<tr><td>Masters:</td><td>{2}</td></tr>
|
<tr><td>Masters:</td><td>{2}</td></tr>
|
||||||
</table>
|
</table>
|
||||||
@ -831,7 +849,7 @@ class DetailedHistory():
|
|||||||
elif 'Delete API key' in history.msg:
|
elif 'Delete API key' in history.msg:
|
||||||
self.detailed_msg = """
|
self.detailed_msg = """
|
||||||
<table class="table table-bordered table-striped">
|
<table class="table table-bordered table-striped">
|
||||||
<tr><td>Key: </td><td>{0}</td></tr>
|
<tr><td>Key: </td><td>{0}</td></tr>
|
||||||
<tr><td>Role:</td><td>{1}</td></tr>
|
<tr><td>Role:</td><td>{1}</td></tr>
|
||||||
<tr><td>Description:</td><td>{2}</td></tr>
|
<tr><td>Description:</td><td>{2}</td></tr>
|
||||||
<tr><td>Accessible domains with this API key:</td><td>{3}</td></tr>
|
<tr><td>Accessible domains with this API key:</td><td>{3}</td></tr>
|
||||||
@ -840,7 +858,7 @@ class DetailedHistory():
|
|||||||
elif 'reverse' in history.msg:
|
elif 'reverse' in history.msg:
|
||||||
self.detailed_msg = """
|
self.detailed_msg = """
|
||||||
<table class="table table-bordered table-striped">
|
<table class="table table-bordered table-striped">
|
||||||
<tr><td>Domain Type: </td><td>{0}</td></tr>
|
<tr><td>Domain Type: </td><td>{0}</td></tr>
|
||||||
<tr><td>Domain Master IPs:</td><td>{1}</td></tr>
|
<tr><td>Domain Master IPs:</td><td>{1}</td></tr>
|
||||||
</table>
|
</table>
|
||||||
""".format(detail_dict['domain_type'], detail_dict['domain_master_ips'])
|
""".format(detail_dict['domain_type'], detail_dict['domain_master_ips'])
|
||||||
@ -895,7 +913,7 @@ def history():
|
|||||||
}), 500)
|
}), 500)
|
||||||
|
|
||||||
|
|
||||||
if request.method == 'GET':
|
if request.method == 'GET':
|
||||||
doms = accounts = users = ""
|
doms = accounts = users = ""
|
||||||
if current_user.role.name in [ 'Administrator', 'Operator']:
|
if current_user.role.name in [ 'Administrator', 'Operator']:
|
||||||
all_domain_names = Domain.query.all()
|
all_domain_names = Domain.query.all()
|
||||||
@ -903,7 +921,7 @@ def history():
|
|||||||
all_user_names = User.query.all()
|
all_user_names = User.query.all()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
for d in all_domain_names:
|
for d in all_domain_names:
|
||||||
doms += d.name + " "
|
doms += d.name + " "
|
||||||
for acc in all_account_names:
|
for acc in all_account_names:
|
||||||
@ -931,9 +949,9 @@ def history():
|
|||||||
AccountUser.user_id == current_user.id
|
AccountUser.user_id == current_user.id
|
||||||
)).all()
|
)).all()
|
||||||
|
|
||||||
|
|
||||||
all_user_names = []
|
all_user_names = []
|
||||||
for a in all_account_names:
|
for a in all_account_names:
|
||||||
temp = db.session.query(User) \
|
temp = db.session.query(User) \
|
||||||
.join(AccountUser, AccountUser.user_id == User.id) \
|
.join(AccountUser, AccountUser.user_id == User.id) \
|
||||||
.outerjoin(Account, Account.id == AccountUser.account_id) \
|
.outerjoin(Account, Account.id == AccountUser.account_id) \
|
||||||
@ -951,11 +969,11 @@ def history():
|
|||||||
|
|
||||||
for d in all_domain_names:
|
for d in all_domain_names:
|
||||||
doms += d.name + " "
|
doms += d.name + " "
|
||||||
|
|
||||||
for a in all_account_names:
|
for a in all_account_names:
|
||||||
accounts += a.name + " "
|
accounts += a.name + " "
|
||||||
for u in all_user_names:
|
for u in all_user_names:
|
||||||
users += u.username + " "
|
users += u.username + " "
|
||||||
return render_template('admin_history.html', all_domain_names=doms, all_account_names=accounts, all_usernames=users)
|
return render_template('admin_history.html', all_domain_names=doms, all_account_names=accounts, all_usernames=users)
|
||||||
|
|
||||||
# local_offset is the offset of the utc to the local time
|
# local_offset is the offset of the utc to the local time
|
||||||
@ -1005,7 +1023,7 @@ def history_table(): # ajax call data
|
|||||||
if current_user.role.name in [ 'Administrator', 'Operator' ]:
|
if current_user.role.name in [ 'Administrator', 'Operator' ]:
|
||||||
base_query = History.query
|
base_query = History.query
|
||||||
else:
|
else:
|
||||||
# if the user isn't an administrator or operator,
|
# if the user isn't an administrator or operator,
|
||||||
# allow_user_view_history must be enabled to get here,
|
# allow_user_view_history must be enabled to get here,
|
||||||
# so include history for the domains for the user
|
# so include history for the domains for the user
|
||||||
base_query = db.session.query(History) \
|
base_query = db.session.query(History) \
|
||||||
@ -1020,7 +1038,7 @@ def history_table(): # ajax call data
|
|||||||
))
|
))
|
||||||
|
|
||||||
domain_name = request.args.get('domain_name_filter') if request.args.get('domain_name_filter') != None \
|
domain_name = request.args.get('domain_name_filter') if request.args.get('domain_name_filter') != None \
|
||||||
and len(request.args.get('domain_name_filter')) != 0 else None
|
and len(request.args.get('domain_name_filter')) != 0 else None
|
||||||
account_name = request.args.get('account_name_filter') if request.args.get('account_name_filter') != None \
|
account_name = request.args.get('account_name_filter') if request.args.get('account_name_filter') != None \
|
||||||
and len(request.args.get('account_name_filter')) != 0 else None
|
and len(request.args.get('account_name_filter')) != 0 else None
|
||||||
user_name = request.args.get('auth_name_filter') if request.args.get('auth_name_filter') != None \
|
user_name = request.args.get('auth_name_filter') if request.args.get('auth_name_filter') != None \
|
||||||
@ -1217,7 +1235,7 @@ def setting_basic():
|
|||||||
'allow_user_create_domain', 'allow_user_remove_domain', 'allow_user_view_history', 'bg_domain_updates', 'site_name',
|
'allow_user_create_domain', 'allow_user_remove_domain', 'allow_user_view_history', 'bg_domain_updates', 'site_name',
|
||||||
'session_timeout', 'warn_session_timeout', 'ttl_options',
|
'session_timeout', 'warn_session_timeout', 'ttl_options',
|
||||||
'pdns_api_timeout', 'verify_ssl_connections', 'verify_user_email',
|
'pdns_api_timeout', 'verify_ssl_connections', 'verify_user_email',
|
||||||
'delete_sso_accounts', 'otp_field_enabled', 'custom_css', 'enable_api_rr_history', 'max_history_records'
|
'delete_sso_accounts', 'otp_field_enabled', 'custom_css', 'enable_api_rr_history', 'max_history_records'
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ from ..lib.errors import (
|
|||||||
DomainNotExists, DomainAlreadyExists, DomainAccessForbidden,
|
DomainNotExists, DomainAlreadyExists, DomainAccessForbidden,
|
||||||
RequestIsNotJSON, ApiKeyCreateFail, ApiKeyNotUsable, NotEnoughPrivileges,
|
RequestIsNotJSON, ApiKeyCreateFail, ApiKeyNotUsable, NotEnoughPrivileges,
|
||||||
AccountCreateFail, AccountUpdateFail, AccountDeleteFail,
|
AccountCreateFail, AccountUpdateFail, AccountDeleteFail,
|
||||||
AccountCreateDuplicate,
|
AccountCreateDuplicate, AccountNotExists,
|
||||||
UserCreateFail, UserCreateDuplicate, UserUpdateFail, UserDeleteFail,
|
UserCreateFail, UserCreateDuplicate, UserUpdateFail, UserDeleteFail,
|
||||||
UserUpdateFailEmail,
|
UserUpdateFailEmail,
|
||||||
)
|
)
|
||||||
@ -307,6 +307,7 @@ def api_generate_apikey():
|
|||||||
role_name = None
|
role_name = None
|
||||||
apikey = None
|
apikey = None
|
||||||
domain_obj_list = []
|
domain_obj_list = []
|
||||||
|
account_obj_list = []
|
||||||
|
|
||||||
abort(400) if 'role' not in data else None
|
abort(400) if 'role' not in data else None
|
||||||
|
|
||||||
@ -317,6 +318,13 @@ def api_generate_apikey():
|
|||||||
else:
|
else:
|
||||||
domains = [d['name'] if isinstance(d, dict) else d for d in data['domains']]
|
domains = [d['name'] if isinstance(d, dict) else d for d in data['domains']]
|
||||||
|
|
||||||
|
if 'accounts' not in data:
|
||||||
|
accounts = []
|
||||||
|
elif not isinstance(data['accounts'], (list, )):
|
||||||
|
abort(400)
|
||||||
|
else:
|
||||||
|
accounts = [a['name'] if isinstance(a, dict) else a for a in data['accounts']]
|
||||||
|
|
||||||
description = data['description'] if 'description' in data else None
|
description = data['description'] if 'description' in data else None
|
||||||
|
|
||||||
if isinstance(data['role'], str):
|
if isinstance(data['role'], str):
|
||||||
@ -326,16 +334,24 @@ def api_generate_apikey():
|
|||||||
else:
|
else:
|
||||||
abort(400)
|
abort(400)
|
||||||
|
|
||||||
if role_name == 'User' and len(domains) == 0:
|
if role_name == 'User' and len(domains) == 0 and len(accounts) == 0:
|
||||||
current_app.logger.error("Apikey with User role must have domains")
|
current_app.logger.error("Apikey with User role must have domains or accounts")
|
||||||
raise ApiKeyNotUsable()
|
raise ApiKeyNotUsable()
|
||||||
elif role_name == 'User':
|
|
||||||
|
if role_name == 'User' and len(domains) > 0:
|
||||||
domain_obj_list = Domain.query.filter(Domain.name.in_(domains)).all()
|
domain_obj_list = Domain.query.filter(Domain.name.in_(domains)).all()
|
||||||
if len(domain_obj_list) == 0:
|
if len(domain_obj_list) == 0:
|
||||||
msg = "One of supplied domains does not exist"
|
msg = "One of supplied domains does not exist"
|
||||||
current_app.logger.error(msg)
|
current_app.logger.error(msg)
|
||||||
raise DomainNotExists(message=msg)
|
raise DomainNotExists(message=msg)
|
||||||
|
|
||||||
|
if role_name == 'User' and len(accounts) > 0:
|
||||||
|
account_obj_list = Account.query.filter(Account.name.in_(accounts)).all()
|
||||||
|
if len(account_obj_list) == 0:
|
||||||
|
msg = "One of supplied accounts does not exist"
|
||||||
|
current_app.logger.error(msg)
|
||||||
|
raise AccountNotExists(message=msg)
|
||||||
|
|
||||||
if current_user.role.name not in ['Administrator', 'Operator']:
|
if current_user.role.name not in ['Administrator', 'Operator']:
|
||||||
# domain list of domain api key should be valid for
|
# domain list of domain api key should be valid for
|
||||||
# if not any domain error
|
# if not any domain error
|
||||||
@ -345,6 +361,11 @@ def api_generate_apikey():
|
|||||||
current_app.logger.error(msg)
|
current_app.logger.error(msg)
|
||||||
raise NotEnoughPrivileges(message=msg)
|
raise NotEnoughPrivileges(message=msg)
|
||||||
|
|
||||||
|
if len(accounts) > 0:
|
||||||
|
msg = "User cannot assign accounts"
|
||||||
|
current_app.logger.error(msg)
|
||||||
|
raise NotEnoughPrivileges(message=msg)
|
||||||
|
|
||||||
user_domain_obj_list = get_user_domains()
|
user_domain_obj_list = get_user_domains()
|
||||||
|
|
||||||
domain_list = [item.name for item in domain_obj_list]
|
domain_list = [item.name for item in domain_obj_list]
|
||||||
@ -363,7 +384,8 @@ def api_generate_apikey():
|
|||||||
|
|
||||||
apikey = ApiKey(desc=description,
|
apikey = ApiKey(desc=description,
|
||||||
role_name=role_name,
|
role_name=role_name,
|
||||||
domains=domain_obj_list)
|
domains=domain_obj_list,
|
||||||
|
accounts=account_obj_list)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
apikey.create()
|
apikey.create()
|
||||||
@ -476,9 +498,16 @@ def api_update_apikey(apikey_id):
|
|||||||
# if role different and user is allowed to change it, update
|
# if role different and user is allowed to change it, update
|
||||||
# if apikey domains are different and user is allowed to handle
|
# if apikey domains are different and user is allowed to handle
|
||||||
# that domains update domains
|
# that domains update domains
|
||||||
|
domain_obj_list = None
|
||||||
|
account_obj_list = None
|
||||||
|
|
||||||
|
apikey = ApiKey.query.get(apikey_id)
|
||||||
|
|
||||||
|
if not apikey:
|
||||||
|
abort(404)
|
||||||
|
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
description = data['description'] if 'description' in data else None
|
description = data['description'] if 'description' in data else None
|
||||||
domain_obj_list = None
|
|
||||||
|
|
||||||
if 'role' in data:
|
if 'role' in data:
|
||||||
if isinstance(data['role'], str):
|
if isinstance(data['role'], str):
|
||||||
@ -487,8 +516,11 @@ def api_update_apikey(apikey_id):
|
|||||||
role_name = data['role']['name']
|
role_name = data['role']['name']
|
||||||
else:
|
else:
|
||||||
abort(400)
|
abort(400)
|
||||||
|
|
||||||
|
target_role = role_name
|
||||||
else:
|
else:
|
||||||
role_name = None
|
role_name = None
|
||||||
|
target_role = apikey.role.name
|
||||||
|
|
||||||
if 'domains' not in data:
|
if 'domains' not in data:
|
||||||
domains = None
|
domains = None
|
||||||
@ -497,22 +529,54 @@ def api_update_apikey(apikey_id):
|
|||||||
else:
|
else:
|
||||||
domains = [d['name'] if isinstance(d, dict) else d for d in data['domains']]
|
domains = [d['name'] if isinstance(d, dict) else d for d in data['domains']]
|
||||||
|
|
||||||
apikey = ApiKey.query.get(apikey_id)
|
if 'accounts' not in data:
|
||||||
|
accounts = None
|
||||||
if not apikey:
|
elif not isinstance(data['accounts'], (list, )):
|
||||||
abort(404)
|
abort(400)
|
||||||
|
else:
|
||||||
|
accounts = [a['name'] if isinstance(a, dict) else a for a in data['accounts']]
|
||||||
|
|
||||||
current_app.logger.debug('Updating apikey with id {0}'.format(apikey_id))
|
current_app.logger.debug('Updating apikey with id {0}'.format(apikey_id))
|
||||||
|
|
||||||
if role_name == 'User' and len(domains) == 0:
|
if target_role == 'User':
|
||||||
current_app.logger.error("Apikey with User role must have domains")
|
current_domains = [item.name for item in apikey.domains]
|
||||||
raise ApiKeyNotUsable()
|
current_accounts = [item.name for item in apikey.accounts]
|
||||||
elif role_name == 'User':
|
|
||||||
domain_obj_list = Domain.query.filter(Domain.name.in_(domains)).all()
|
if domains is not None:
|
||||||
if len(domain_obj_list) == 0:
|
domain_obj_list = Domain.query.filter(Domain.name.in_(domains)).all()
|
||||||
msg = "One of supplied domains does not exist"
|
if len(domain_obj_list) != len(domains):
|
||||||
current_app.logger.error(msg)
|
msg = "One of supplied domains does not exist"
|
||||||
raise DomainNotExists(message=msg)
|
current_app.logger.error(msg)
|
||||||
|
raise DomainNotExists(message=msg)
|
||||||
|
|
||||||
|
target_domains = domains
|
||||||
|
else:
|
||||||
|
target_domains = current_domains
|
||||||
|
|
||||||
|
if accounts is not None:
|
||||||
|
account_obj_list = Account.query.filter(Account.name.in_(accounts)).all()
|
||||||
|
if len(account_obj_list) != len(accounts):
|
||||||
|
msg = "One of supplied accounts does not exist"
|
||||||
|
current_app.logger.error(msg)
|
||||||
|
raise AccountNotExists(message=msg)
|
||||||
|
|
||||||
|
target_accounts = accounts
|
||||||
|
else:
|
||||||
|
target_accounts = current_accounts
|
||||||
|
|
||||||
|
if len(target_domains) == 0 and len(target_accounts) == 0:
|
||||||
|
current_app.logger.error("Apikey with User role must have domains or accounts")
|
||||||
|
raise ApiKeyNotUsable()
|
||||||
|
|
||||||
|
if domains is not None and set(domains) == set(current_domains):
|
||||||
|
current_app.logger.debug(
|
||||||
|
"Domains are the same, apikey domains won't be updated")
|
||||||
|
domains = None
|
||||||
|
|
||||||
|
if accounts is not None and set(accounts) == set(current_accounts):
|
||||||
|
current_app.logger.debug(
|
||||||
|
"Accounts are the same, apikey accounts won't be updated")
|
||||||
|
accounts = None
|
||||||
|
|
||||||
if current_user.role.name not in ['Administrator', 'Operator']:
|
if current_user.role.name not in ['Administrator', 'Operator']:
|
||||||
if role_name != 'User':
|
if role_name != 'User':
|
||||||
@ -520,8 +584,12 @@ def api_update_apikey(apikey_id):
|
|||||||
current_app.logger.error(msg)
|
current_app.logger.error(msg)
|
||||||
raise NotEnoughPrivileges(message=msg)
|
raise NotEnoughPrivileges(message=msg)
|
||||||
|
|
||||||
|
if len(accounts) > 0:
|
||||||
|
msg = "User cannot assign accounts"
|
||||||
|
current_app.logger.error(msg)
|
||||||
|
raise NotEnoughPrivileges(message=msg)
|
||||||
|
|
||||||
apikeys = get_user_apikeys()
|
apikeys = get_user_apikeys()
|
||||||
apikey_domains = [item.name for item in apikey.domains]
|
|
||||||
apikeys_ids = [apikey_item.id for apikey_item in apikeys]
|
apikeys_ids = [apikey_item.id for apikey_item in apikeys]
|
||||||
|
|
||||||
user_domain_obj_list = current_user.get_domain().all()
|
user_domain_obj_list = current_user.get_domain().all()
|
||||||
@ -545,12 +613,7 @@ def api_update_apikey(apikey_id):
|
|||||||
current_app.logger.error(msg)
|
current_app.logger.error(msg)
|
||||||
raise DomainAccessForbidden()
|
raise DomainAccessForbidden()
|
||||||
|
|
||||||
if set(domains) == set(apikey_domains):
|
if role_name == apikey.role.name:
|
||||||
current_app.logger.debug(
|
|
||||||
"Domains are same, apikey domains won't be updated")
|
|
||||||
domains = None
|
|
||||||
|
|
||||||
if role_name == apikey.role:
|
|
||||||
current_app.logger.debug("Role is same, apikey role won't be updated")
|
current_app.logger.debug("Role is same, apikey role won't be updated")
|
||||||
role_name = None
|
role_name = None
|
||||||
|
|
||||||
@ -559,10 +622,13 @@ def api_update_apikey(apikey_id):
|
|||||||
current_app.logger.debug(msg)
|
current_app.logger.debug(msg)
|
||||||
description = None
|
description = None
|
||||||
|
|
||||||
|
if target_role != "User":
|
||||||
|
domains, accounts = [], []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
apikey = ApiKey.query.get(apikey_id)
|
|
||||||
apikey.update(role_name=role_name,
|
apikey.update(role_name=role_name,
|
||||||
domains=domains,
|
domains=domains,
|
||||||
|
accounts=accounts,
|
||||||
description=description)
|
description=description)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.error('Error: {0}'.format(e))
|
current_app.logger.error('Error: {0}'.format(e))
|
||||||
@ -856,7 +922,7 @@ def api_update_account(account_id):
|
|||||||
"Updating account {} ({})".format(account_id, account.name))
|
"Updating account {} ({})".format(account_id, account.name))
|
||||||
result = account.update_account()
|
result = account.update_account()
|
||||||
if not result['status']:
|
if not result['status']:
|
||||||
raise AccountDeleteFail(message=result['msg'])
|
raise AccountUpdateFail(message=result['msg'])
|
||||||
history = History(msg='Update account {0}'.format(account.name),
|
history = History(msg='Update account {0}'.format(account.name),
|
||||||
created_by=current_user.username)
|
created_by=current_user.username)
|
||||||
history.add()
|
history.add()
|
||||||
@ -876,7 +942,7 @@ def api_delete_account(account_id):
|
|||||||
"Deleting account {} ({})".format(account_id, account.name))
|
"Deleting account {} ({})".format(account_id, account.name))
|
||||||
result = account.delete_account()
|
result = account.delete_account()
|
||||||
if not result:
|
if not result:
|
||||||
raise AccountUpdateFail(message=result['msg'])
|
raise AccountDeleteFail(message=result['msg'])
|
||||||
|
|
||||||
history = History(msg='Delete account {0}'.format(account.name),
|
history = History(msg='Delete account {0}'.format(account.name),
|
||||||
created_by=current_user.username)
|
created_by=current_user.username)
|
||||||
@ -1055,8 +1121,13 @@ def api_get_zones(server_id):
|
|||||||
and resp.status_code == 200):
|
and resp.status_code == 200):
|
||||||
domain_list = [d['name']
|
domain_list = [d['name']
|
||||||
for d in domain_schema.dump(g.apikey.domains)]
|
for d in domain_schema.dump(g.apikey.domains)]
|
||||||
|
|
||||||
|
accounts_domains = [d.name for a in g.apikey.accounts for d in a.domains]
|
||||||
|
allowed_domains = set(domain_list + accounts_domains)
|
||||||
|
current_app.logger.debug("Account domains: {}".format(
|
||||||
|
'/'.join(accounts_domains)))
|
||||||
content = json.dumps([i for i in json.loads(resp.content)
|
content = json.dumps([i for i in json.loads(resp.content)
|
||||||
if i['name'].rstrip('.') in domain_list])
|
if i['name'].rstrip('.') in allowed_domains])
|
||||||
return content, resp.status_code, resp.headers.items()
|
return content, resp.status_code, resp.headers.items()
|
||||||
else:
|
else:
|
||||||
return resp.content, resp.status_code, resp.headers.items()
|
return resp.content, resp.status_code, resp.headers.items()
|
||||||
|
@ -797,6 +797,11 @@ paths:
|
|||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/PDNSAdminZones'
|
$ref: '#/definitions/PDNSAdminZones'
|
||||||
|
'401':
|
||||||
|
description: 'Unauthorized'
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error'
|
||||||
|
|
||||||
post:
|
post:
|
||||||
security:
|
security:
|
||||||
- basicAuth: []
|
- basicAuth: []
|
||||||
@ -816,6 +821,23 @@ paths:
|
|||||||
description: A zone
|
description: A zone
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/Zone'
|
$ref: '#/definitions/Zone'
|
||||||
|
'400':
|
||||||
|
description: 'Request is not JSON'
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error'
|
||||||
|
'401':
|
||||||
|
description: 'Unauthorized'
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error'
|
||||||
|
'409':
|
||||||
|
description: 'Domain already exists (conflict)'
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error'
|
||||||
|
'500':
|
||||||
|
description: 'Internal Server Error'
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error'
|
||||||
|
|
||||||
'/pdnsadmin/zones/{zone_id}':
|
'/pdnsadmin/zones/{zone_id}':
|
||||||
parameters:
|
parameters:
|
||||||
- name: zone_id
|
- name: zone_id
|
||||||
@ -839,6 +861,23 @@ paths:
|
|||||||
responses:
|
responses:
|
||||||
'204':
|
'204':
|
||||||
description: 'Returns 204 No Content on success.'
|
description: 'Returns 204 No Content on success.'
|
||||||
|
'401':
|
||||||
|
description: 'Unauthorized'
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error'
|
||||||
|
'403':
|
||||||
|
description: 'Forbidden'
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error'
|
||||||
|
'404':
|
||||||
|
description: 'Not found'
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error'
|
||||||
|
'500':
|
||||||
|
description: 'Internal Server Error'
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error'
|
||||||
|
|
||||||
'/pdnsadmin/apikeys':
|
'/pdnsadmin/apikeys':
|
||||||
get:
|
get:
|
||||||
security:
|
security:
|
||||||
@ -854,15 +893,23 @@ paths:
|
|||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/ApiKey'
|
$ref: '#/definitions/ApiKey'
|
||||||
|
'401':
|
||||||
|
description: 'Unauthorized'
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error'
|
||||||
|
'403':
|
||||||
|
description: 'Domain Access Forbidden'
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error'
|
||||||
'500':
|
'500':
|
||||||
description: 'Internal Server Error, keys could not be retrieved. Contains error message'
|
description: 'Internal Server Error. There was a problem creating the key'
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/Error'
|
$ref: '#/definitions/Error'
|
||||||
post:
|
post:
|
||||||
security:
|
security:
|
||||||
- basicAuth: []
|
- basicAuth: []
|
||||||
summary: 'Add a ApiKey key'
|
summary: 'Add a ApiKey key'
|
||||||
description: 'This methods add a new ApiKey. The actual key can be generated by the server or be provided by the client'
|
description: 'This methods add a new ApiKey. The actual key is generated by the server'
|
||||||
operationId: api_generate_apikey
|
operationId: api_generate_apikey
|
||||||
tags:
|
tags:
|
||||||
- apikey
|
- apikey
|
||||||
@ -878,14 +925,27 @@ paths:
|
|||||||
description: Created
|
description: Created
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/ApiKey'
|
$ref: '#/definitions/ApiKey'
|
||||||
'422':
|
'400':
|
||||||
description: 'Unprocessable Entry, the ApiKey provided has issues.'
|
description: 'Request is not JSON or does not respect required format'
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error'
|
||||||
|
'401':
|
||||||
|
description: 'Unauthorized'
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error'
|
||||||
|
'403':
|
||||||
|
description: 'Domain Access Forbidden'
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error'
|
||||||
|
'404':
|
||||||
|
description: 'Domain or Account Not found'
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/Error'
|
$ref: '#/definitions/Error'
|
||||||
'500':
|
'500':
|
||||||
description: 'Internal Server Error. There was a problem creating the key'
|
description: 'Internal Server Error. There was a problem creating the key'
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/Error'
|
$ref: '#/definitions/Error'
|
||||||
|
|
||||||
'/pdnsadmin/apikeys/{apikey_id}':
|
'/pdnsadmin/apikeys/{apikey_id}':
|
||||||
parameters:
|
parameters:
|
||||||
- name: apikey_id
|
- name: apikey_id
|
||||||
@ -905,14 +965,16 @@ paths:
|
|||||||
description: OK.
|
description: OK.
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/ApiKey'
|
$ref: '#/definitions/ApiKey'
|
||||||
'403':
|
'401':
|
||||||
description: 'The authenticated user has User role and is not allowed on any of the domains assigned to the key'
|
description: 'Unauthorized'
|
||||||
'404':
|
|
||||||
description: 'Not found. The ApiKey with the specified apikey_id does not exist'
|
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/Error'
|
$ref: '#/definitions/Error'
|
||||||
'500':
|
'403':
|
||||||
description: 'Internal Server Error, keys could not be retrieved. Contains error message'
|
description: 'The authenticated user has User role and is not allowed on any of the domains assigned to the key'
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error'
|
||||||
|
'404':
|
||||||
|
description: 'Not found. The ApiKey with the specified apikey_id does not exist'
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/Error'
|
$ref: '#/definitions/Error'
|
||||||
delete:
|
delete:
|
||||||
@ -925,6 +987,14 @@ paths:
|
|||||||
responses:
|
responses:
|
||||||
'204':
|
'204':
|
||||||
description: 'OK, key was deleted'
|
description: 'OK, key was deleted'
|
||||||
|
'401':
|
||||||
|
description: 'Unauthorized'
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error'
|
||||||
|
'403':
|
||||||
|
description: 'The authenticated user has User role and is not allowed on any of the domains assigned to the key'
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error'
|
||||||
'404':
|
'404':
|
||||||
description: 'Not found. The ApiKey with the specified apikey_id does not exist'
|
description: 'Not found. The ApiKey with the specified apikey_id does not exist'
|
||||||
schema:
|
schema:
|
||||||
@ -938,9 +1008,11 @@ paths:
|
|||||||
- basicAuth: []
|
- basicAuth: []
|
||||||
description: |
|
description: |
|
||||||
The ApiKey at apikey_id can be changed in multiple ways:
|
The ApiKey at apikey_id can be changed in multiple ways:
|
||||||
* Role, description, domains can be updated
|
* Role, description, accounts and domains can be updated
|
||||||
* Role can be changed to Administrator only if user has Operator or Administrator privileges
|
* Role can be changed to Administrator only if user has Operator or Administrator privileges
|
||||||
* Domains will be updated only if user has access to them
|
* Domains will be updated only if user has access to them
|
||||||
|
* Accounts can be updated only by a privileged user
|
||||||
|
* With a User role, an ApiKey needs at least one account or one domain
|
||||||
Only the relevant fields have to be provided in the request body.
|
Only the relevant fields have to be provided in the request body.
|
||||||
operationId: api_update_apikey
|
operationId: api_update_apikey
|
||||||
tags:
|
tags:
|
||||||
@ -957,14 +1029,27 @@ paths:
|
|||||||
description: OK. ApiKey is changed.
|
description: OK. ApiKey is changed.
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/ApiKey'
|
$ref: '#/definitions/ApiKey'
|
||||||
|
'400':
|
||||||
|
description: 'Request is not JSON'
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error'
|
||||||
|
'401':
|
||||||
|
description: 'Unauthorized'
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error'
|
||||||
|
'403':
|
||||||
|
description: 'Domain Access Forbidden'
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error'
|
||||||
'404':
|
'404':
|
||||||
description: 'Not found. The TSIGKey with the specified tsigkey_id does not exist'
|
description: 'Not found (ApiKey, Domain or Account)'
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/Error'
|
$ref: '#/definitions/Error'
|
||||||
'500':
|
'500':
|
||||||
description: 'Internal Server Error. Contains error message'
|
description: 'Internal Server Error. Contains error message'
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/Error'
|
$ref: '#/definitions/Error'
|
||||||
|
|
||||||
'/pdnsadmin/users':
|
'/pdnsadmin/users':
|
||||||
get:
|
get:
|
||||||
security:
|
security:
|
||||||
@ -980,6 +1065,10 @@ paths:
|
|||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/User'
|
$ref: '#/definitions/User'
|
||||||
|
'401':
|
||||||
|
description: 'Unauthorized'
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error'
|
||||||
'500':
|
'500':
|
||||||
description: Internal Server Error, users could not be retrieved. Contains error message
|
description: Internal Server Error, users could not be retrieved. Contains error message
|
||||||
schema:
|
schema:
|
||||||
@ -1038,7 +1127,11 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/User'
|
$ref: '#/definitions/User'
|
||||||
'400':
|
'400':
|
||||||
description: Unprocessable Entry, the User data provided has issues
|
description: 'Request is not JSON'
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error'
|
||||||
|
'401':
|
||||||
|
description: 'Unauthorized'
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/Error'
|
$ref: '#/definitions/Error'
|
||||||
'409':
|
'409':
|
||||||
@ -1049,6 +1142,7 @@ paths:
|
|||||||
description: Internal Server Error. There was a problem creating the user
|
description: Internal Server Error. There was a problem creating the user
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/Error'
|
$ref: '#/definitions/Error'
|
||||||
|
|
||||||
'/pdnsadmin/users/{username}':
|
'/pdnsadmin/users/{username}':
|
||||||
parameters:
|
parameters:
|
||||||
- name: username
|
- name: username
|
||||||
@ -1068,6 +1162,10 @@ paths:
|
|||||||
description: Retrieve a specific User
|
description: Retrieve a specific User
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/UserDetailed'
|
$ref: '#/definitions/UserDetailed'
|
||||||
|
'401':
|
||||||
|
description: 'Unauthorized'
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error'
|
||||||
'404':
|
'404':
|
||||||
description: Not found. The User with the specified username does not exist
|
description: Not found. The User with the specified username does not exist
|
||||||
schema:
|
schema:
|
||||||
@ -1076,6 +1174,7 @@ paths:
|
|||||||
description: Internal Server Error, user could not be retrieved. Contains error message
|
description: Internal Server Error, user could not be retrieved. Contains error message
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/Error'
|
$ref: '#/definitions/Error'
|
||||||
|
|
||||||
'/pdnsadmin/users/{user_id}':
|
'/pdnsadmin/users/{user_id}':
|
||||||
parameters:
|
parameters:
|
||||||
- name: user_id
|
- name: user_id
|
||||||
@ -1129,10 +1228,22 @@ paths:
|
|||||||
responses:
|
responses:
|
||||||
'204':
|
'204':
|
||||||
description: OK. User is modified (empty response body)
|
description: OK. User is modified (empty response body)
|
||||||
|
'400':
|
||||||
|
description: 'Request is not JSON'
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error'
|
||||||
|
'401':
|
||||||
|
description: 'Unauthorized'
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error'
|
||||||
'404':
|
'404':
|
||||||
description: Not found. The User with the specified user_id does not exist
|
description: Not found. The User with the specified user_id does not exist
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/Error'
|
$ref: '#/definitions/Error'
|
||||||
|
'409':
|
||||||
|
description: Duplicate (Email already assigned to another user)
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error'
|
||||||
'500':
|
'500':
|
||||||
description: Internal Server Error. Contains error message
|
description: Internal Server Error. Contains error message
|
||||||
schema:
|
schema:
|
||||||
@ -1147,6 +1258,10 @@ paths:
|
|||||||
responses:
|
responses:
|
||||||
'204':
|
'204':
|
||||||
description: OK. User is deleted (empty response body)
|
description: OK. User is deleted (empty response body)
|
||||||
|
'401':
|
||||||
|
description: 'Unauthorized'
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error'
|
||||||
'404':
|
'404':
|
||||||
description: Not found. The User with the specified user_id does not exist
|
description: Not found. The User with the specified user_id does not exist
|
||||||
schema:
|
schema:
|
||||||
@ -1155,6 +1270,7 @@ paths:
|
|||||||
description: Internal Server Error. Contains error message
|
description: Internal Server Error. Contains error message
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/Error'
|
$ref: '#/definitions/Error'
|
||||||
|
|
||||||
'/pdnsadmin/accounts':
|
'/pdnsadmin/accounts':
|
||||||
get:
|
get:
|
||||||
security:
|
security:
|
||||||
@ -1170,8 +1286,8 @@ paths:
|
|||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/Account'
|
$ref: '#/definitions/Account'
|
||||||
'500':
|
'401':
|
||||||
description: Internal Server Error, accounts could not be retrieved. Contains error message
|
description: 'Unauthorized'
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/Error'
|
$ref: '#/definitions/Error'
|
||||||
post:
|
post:
|
||||||
@ -1207,7 +1323,11 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/Account'
|
$ref: '#/definitions/Account'
|
||||||
'400':
|
'400':
|
||||||
description: Unprocessable Entry, the Account data provided has issues.
|
description: 'Request is not JSON'
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error'
|
||||||
|
'401':
|
||||||
|
description: 'Unauthorized'
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/Error'
|
$ref: '#/definitions/Error'
|
||||||
'409':
|
'409':
|
||||||
@ -1218,6 +1338,7 @@ paths:
|
|||||||
description: Internal Server Error. There was a problem creating the account
|
description: Internal Server Error. There was a problem creating the account
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/Error'
|
$ref: '#/definitions/Error'
|
||||||
|
|
||||||
'/pdnsadmin/accounts/{account_name}':
|
'/pdnsadmin/accounts/{account_name}':
|
||||||
parameters:
|
parameters:
|
||||||
- name: account_name
|
- name: account_name
|
||||||
@ -1237,14 +1358,15 @@ paths:
|
|||||||
description: Retrieve a specific account
|
description: Retrieve a specific account
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/Account'
|
$ref: '#/definitions/Account'
|
||||||
|
'401':
|
||||||
|
description: 'Unauthorized'
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error'
|
||||||
'404':
|
'404':
|
||||||
description: Not found. The Account with the specified name does not exist
|
description: Not found. The Account with the specified name does not exist
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/Error'
|
$ref: '#/definitions/Error'
|
||||||
'500':
|
|
||||||
description: Internal Server Error, account could not be retrieved. Contains error message
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/Error'
|
|
||||||
'/pdnsadmin/accounts/{account_id}':
|
'/pdnsadmin/accounts/{account_id}':
|
||||||
parameters:
|
parameters:
|
||||||
- name: account_id
|
- name: account_id
|
||||||
@ -1281,6 +1403,14 @@ paths:
|
|||||||
responses:
|
responses:
|
||||||
'204':
|
'204':
|
||||||
description: OK. Account is modified (empty response body)
|
description: OK. Account is modified (empty response body)
|
||||||
|
'400':
|
||||||
|
description: 'Request is not JSON'
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error'
|
||||||
|
'401':
|
||||||
|
description: 'Unauthorized'
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error'
|
||||||
'404':
|
'404':
|
||||||
description: Not found. The Account with the specified account_id does not exist
|
description: Not found. The Account with the specified account_id does not exist
|
||||||
schema:
|
schema:
|
||||||
@ -1299,6 +1429,10 @@ paths:
|
|||||||
responses:
|
responses:
|
||||||
'204':
|
'204':
|
||||||
description: OK. Account is deleted (empty response body)
|
description: OK. Account is deleted (empty response body)
|
||||||
|
'401':
|
||||||
|
description: 'Unauthorized'
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error'
|
||||||
'404':
|
'404':
|
||||||
description: Not found. The Account with the specified account_id does not exist
|
description: Not found. The Account with the specified account_id does not exist
|
||||||
schema:
|
schema:
|
||||||
@ -1307,6 +1441,7 @@ paths:
|
|||||||
description: Internal Server Error. Contains error message
|
description: Internal Server Error. Contains error message
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/Error'
|
$ref: '#/definitions/Error'
|
||||||
|
|
||||||
'/pdnsadmin/accounts/{account_id}/users':
|
'/pdnsadmin/accounts/{account_id}/users':
|
||||||
parameters:
|
parameters:
|
||||||
- name: account_id
|
- name: account_id
|
||||||
@ -1329,14 +1464,46 @@ paths:
|
|||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/User'
|
$ref: '#/definitions/User'
|
||||||
|
'401':
|
||||||
|
description: 'Unauthorized'
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error'
|
||||||
'404':
|
'404':
|
||||||
description: Not found. The Account with the specified account_id does not exist
|
description: Not found. The Account with the specified account_id does not exist
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/Error'
|
$ref: '#/definitions/Error'
|
||||||
'500':
|
|
||||||
description: Internal Server Error, accounts could not be retrieved. Contains error message
|
'/pdnsadmin/accounts/users/{account_id}':
|
||||||
|
parameters:
|
||||||
|
- name: account_id
|
||||||
|
type: integer
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
description: The id of the account to list users linked to account
|
||||||
|
get:
|
||||||
|
security:
|
||||||
|
- basicAuth: []
|
||||||
|
summary: List users linked to a specific account
|
||||||
|
operationId: api_list_users_account
|
||||||
|
tags:
|
||||||
|
- account
|
||||||
|
- user
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: List of Summarized User objects
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/User'
|
||||||
|
'401':
|
||||||
|
description: 'Unauthorized'
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/Error'
|
$ref: '#/definitions/Error'
|
||||||
|
'404':
|
||||||
|
description: Not found. The Account with the specified account_id does not exist
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error'
|
||||||
|
|
||||||
'/pdnsadmin/accounts/{account_id}/users/{user_id}':
|
'/pdnsadmin/accounts/{account_id}/users/{user_id}':
|
||||||
parameters:
|
parameters:
|
||||||
- name: account_id
|
- name: account_id
|
||||||
@ -1360,6 +1527,14 @@ paths:
|
|||||||
responses:
|
responses:
|
||||||
'204':
|
'204':
|
||||||
description: OK. User is linked (empty response body)
|
description: OK. User is linked (empty response body)
|
||||||
|
'400':
|
||||||
|
description: 'Request is not JSON'
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error'
|
||||||
|
'401':
|
||||||
|
description: 'Unauthorized'
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error'
|
||||||
'404':
|
'404':
|
||||||
description: Not found. The Account or User with the specified id does not exist
|
description: Not found. The Account or User with the specified id does not exist
|
||||||
schema:
|
schema:
|
||||||
@ -1379,6 +1554,73 @@ paths:
|
|||||||
responses:
|
responses:
|
||||||
'204':
|
'204':
|
||||||
description: OK. User is unlinked (empty response body)
|
description: OK. User is unlinked (empty response body)
|
||||||
|
'401':
|
||||||
|
description: 'Unauthorized'
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error'
|
||||||
|
'404':
|
||||||
|
description: Not found. The Account or User with the specified id does not exist or user was not linked to account
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error'
|
||||||
|
'500':
|
||||||
|
description: Internal Server Error. Contains error message
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error'
|
||||||
|
|
||||||
|
'/pdnsadmin/accounts/users/{account_id}/{user_id}':
|
||||||
|
parameters:
|
||||||
|
- name: account_id
|
||||||
|
type: integer
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
description: The id of the account to link/unlink users to account
|
||||||
|
- name: user_id
|
||||||
|
type: integer
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
description: The id of the user to (un)link to/from account
|
||||||
|
put:
|
||||||
|
security:
|
||||||
|
- basicAuth: []
|
||||||
|
summary: Link user to account
|
||||||
|
operationId: api_add_user_account
|
||||||
|
tags:
|
||||||
|
- account
|
||||||
|
- user
|
||||||
|
responses:
|
||||||
|
'204':
|
||||||
|
description: OK. User is linked (empty response body)
|
||||||
|
'400':
|
||||||
|
description: 'Request is not JSON'
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error'
|
||||||
|
'401':
|
||||||
|
description: 'Unauthorized'
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error'
|
||||||
|
'404':
|
||||||
|
description: Not found. The Account or User with the specified id does not exist
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error'
|
||||||
|
'500':
|
||||||
|
description: Internal Server Error. Contains error message
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error'
|
||||||
|
delete:
|
||||||
|
security:
|
||||||
|
- basicAuth: []
|
||||||
|
summary: Unlink user from account
|
||||||
|
operationId: api_remove_user_account
|
||||||
|
tags:
|
||||||
|
- account
|
||||||
|
- user
|
||||||
|
responses:
|
||||||
|
'204':
|
||||||
|
description: OK. User is unlinked (empty response body)
|
||||||
|
'401':
|
||||||
|
description: 'Unauthorized'
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error'
|
||||||
'404':
|
'404':
|
||||||
description: Not found. The Account or User with the specified id does not exist or user was not linked to account
|
description: Not found. The Account or User with the specified id does not exist or user was not linked to account
|
||||||
schema:
|
schema:
|
||||||
@ -1598,8 +1840,9 @@ definitions:
|
|||||||
|
|
||||||
PDNSAdminZones:
|
PDNSAdminZones:
|
||||||
title: PDNSAdminZones
|
title: PDNSAdminZones
|
||||||
description: A ApiKey that can be used to manage domains through API
|
description: 'A list of domains'
|
||||||
type: array
|
type: array
|
||||||
|
x-omitempty: false
|
||||||
items:
|
items:
|
||||||
properties:
|
properties:
|
||||||
id:
|
id:
|
||||||
@ -1624,7 +1867,7 @@ definitions:
|
|||||||
|
|
||||||
ApiKey:
|
ApiKey:
|
||||||
title: ApiKey
|
title: ApiKey
|
||||||
description: A ApiKey that can be used to manage domains through API
|
description: 'An ApiKey that can be used to manage domains through API'
|
||||||
properties:
|
properties:
|
||||||
id:
|
id:
|
||||||
type: integer
|
type: integer
|
||||||
@ -1644,6 +1887,23 @@ definitions:
|
|||||||
description:
|
description:
|
||||||
type: string
|
type: string
|
||||||
description: 'Some user defined description'
|
description: 'Some user defined description'
|
||||||
|
accounts:
|
||||||
|
type: array
|
||||||
|
description: 'A list of accounts bound to this ApiKey'
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/AccountSummary'
|
||||||
|
|
||||||
|
ApiKeySummary:
|
||||||
|
title: ApiKeySummary
|
||||||
|
description: Summary of an ApiKey
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
description: 'The ID for this key, used in the ApiKey URL endpoint.'
|
||||||
|
readOnly: true
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
description: 'Some user defined description'
|
||||||
|
|
||||||
User:
|
User:
|
||||||
title: User
|
title: User
|
||||||
@ -1751,6 +2011,12 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
description: The email address of the contact for this account
|
description: The email address of the contact for this account
|
||||||
readOnly: false
|
readOnly: false
|
||||||
|
apikeys:
|
||||||
|
type: array
|
||||||
|
description: A list of API Keys bound to this account
|
||||||
|
readOnly: true
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/ApiKeySummary'
|
||||||
|
|
||||||
AccountSummary:
|
AccountSummary:
|
||||||
title: AccountSummry
|
title: AccountSummry
|
||||||
@ -1764,6 +2030,12 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
description: The name for this account (unique, immutable)
|
description: The name for this account (unique, immutable)
|
||||||
readOnly: false
|
readOnly: false
|
||||||
|
domains:
|
||||||
|
type: array
|
||||||
|
description: The list of domains owned by this account
|
||||||
|
readOnly: true
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/PDNSAdminZones'
|
||||||
|
|
||||||
ConfigSetting:
|
ConfigSetting:
|
||||||
title: ConfigSetting
|
title: ConfigSetting
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% set active_page = "admin_keys" %}
|
{% set active_page = "admin_keys" %}
|
||||||
|
{% if create or (key is not none and key.role.name != "User") %}{% set hide_opts = True %}{%else %}{% set hide_opts = False %}{% endif %}
|
||||||
{% block title %}
|
{% block title %}
|
||||||
<title>Edit Key - {{ SITE_NAME }}</title>
|
<title>Edit Key - {{ SITE_NAME }}</title>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -49,10 +50,26 @@
|
|||||||
class="glyphicon glyphicon-pencil form-control-feedback"></span>
|
class="glyphicon glyphicon-pencil form-control-feedback"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="box-header with-border">
|
<div class="box-header with-border key-opts"{% if hide_opts %} style="display: none;"{% endif %}>
|
||||||
<h3 class="box-title">Access Control</h3>
|
<h3 class="box-title">Accounts Access Control</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="box-body">
|
<div class="box-body key-opts"{% if hide_opts %} style="display: none;"{% endif %}>
|
||||||
|
<p>This key will be linked to the accounts on the right,</p>
|
||||||
|
<p>thus granting access to domains owned by the selected accounts.</p>
|
||||||
|
<p>Click on accounts to move between the columns.</p>
|
||||||
|
<div class="form-group col-xs-2">
|
||||||
|
<select multiple="multiple" class="form-control" id="key_multi_account"
|
||||||
|
name="key_multi_account">
|
||||||
|
{% for account in accounts %}
|
||||||
|
<option {% if key and account in key.accounts %}selected{% endif %} value="{{ account.name }}">{{ account.name }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="box-header with-border key-opts"{% if hide_opts %} style="display: none;"{% endif %}>
|
||||||
|
<h3 class="box-title">Domain Access Control</h3>
|
||||||
|
</div>
|
||||||
|
<div class="box-body key-opts"{% if hide_opts %} style="display: none;"{% endif %}>
|
||||||
<p>This key will have acess to the domains on the right.</p>
|
<p>This key will have acess to the domains on the right.</p>
|
||||||
<p>Click on domains to move between the columns.</p>
|
<p>Click on domains to move between the columns.</p>
|
||||||
<div class="form-group col-xs-2">
|
<div class="form-group col-xs-2">
|
||||||
@ -66,7 +83,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="box-footer">
|
<div class="box-footer">
|
||||||
<button type="submit"
|
<button type="submit"
|
||||||
class="btn btn-flat btn-primary">{% if create %}Create{% else %}Update{% endif %}
|
class="btn btn-flat btn-primary" id="key_submit">{% if create %}Create{% else %}Update{% endif %}
|
||||||
Key</button>
|
Key</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@ -82,7 +99,7 @@
|
|||||||
<p>Fill in all the fields in the form to the left.</p>
|
<p>Fill in all the fields in the form to the left.</p>
|
||||||
<p><strong>Role</strong> The role of the key.</p>
|
<p><strong>Role</strong> The role of the key.</p>
|
||||||
<p><strong>Description</strong> The key description.</p>
|
<p><strong>Description</strong> The key description.</p>
|
||||||
<p><strong>Access Control</strong> The domains which the key has access to.</p>
|
<p><strong>Access Control</strong> The domains or accounts which the key has access to.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -91,6 +108,48 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block extrascripts %}
|
{% block extrascripts %}
|
||||||
<script>
|
<script>
|
||||||
|
$('form').submit(function (e) {
|
||||||
|
var selectedRole = $("#key_role").val();
|
||||||
|
var selectedDomains = $("#key_multi_domain option:selected").length;
|
||||||
|
var selectedAccounts = $("#key_multi_account option:selected").length;
|
||||||
|
var warn_modal = $("#modal_warning");
|
||||||
|
|
||||||
|
if (selectedRole != "User" && selectedDomains > 0 && selectedAccounts > 0){
|
||||||
|
var warning = "Administrator and Operators have access to all domains. Your domain an account selection won't be saved.";
|
||||||
|
e.preventDefault(e);
|
||||||
|
warn_modal.modal('show');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedRole == "User" && selectedDomains == 0 && selectedAccounts == 0){
|
||||||
|
var warning = "User role must have at least one account or one domain bound. None selected.";
|
||||||
|
e.preventDefault(e);
|
||||||
|
warn_modal.modal('show');
|
||||||
|
}
|
||||||
|
|
||||||
|
warn_modal.find('.modal-body p').text(warning);
|
||||||
|
warn_modal.find('#button_key_confirm_warn').click(clearModal);
|
||||||
|
});
|
||||||
|
function clearModal(){
|
||||||
|
$("#modal_warning").modal('hide');
|
||||||
|
}
|
||||||
|
$('#key_role').on('change', function (e) {
|
||||||
|
var optionSelected = $("option:selected", this);
|
||||||
|
if (this.value != "User") {
|
||||||
|
// Clear the visible list
|
||||||
|
$('#ms-key_multi_domain .ms-selection li').each(function(){ $(this).css('display', 'none');})
|
||||||
|
$('#ms-key_multi_domain .ms-selectable li').each(function(){ $(this).css('display', '');})
|
||||||
|
$('#ms-key_multi_account .ms-selection li').each(function(){ $(this).css('display', 'none');})
|
||||||
|
$('#ms-key_multi_account .ms-selectable li').each(function(){ $(this).css('display', '');})
|
||||||
|
// Deselect invisible selectbox
|
||||||
|
$('#key_multi_domain option:selected').each(function(){ $(this).prop('selected', false);})
|
||||||
|
$('#key_multi_account option:selected').each(function(){ $(this).prop('selected', false);})
|
||||||
|
// Hide the lists
|
||||||
|
$(".key-opts").hide();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$(".key-opts").show();
|
||||||
|
}
|
||||||
|
});
|
||||||
$("#key_multi_domain").multiSelect({
|
$("#key_multi_domain").multiSelect({
|
||||||
selectableHeader: "<input type='text' class='search-input' autocomplete='off' placeholder='Domain Name'>",
|
selectableHeader: "<input type='text' class='search-input' autocomplete='off' placeholder='Domain Name'>",
|
||||||
selectionHeader: "<input type='text' class='search-input' autocomplete='off' placeholder='Domain Name'>",
|
selectionHeader: "<input type='text' class='search-input' autocomplete='off' placeholder='Domain Name'>",
|
||||||
@ -126,6 +185,41 @@
|
|||||||
this.qs2.cache();
|
this.qs2.cache();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
$("#key_multi_account").multiSelect({
|
||||||
|
selectableHeader: "<input type='text' class='search-input' autocomplete='off' placeholder='Account Name'>",
|
||||||
|
selectionHeader: "<input type='text' class='search-input' autocomplete='off' placeholder='Account Name'>",
|
||||||
|
afterInit: function (ms) {
|
||||||
|
var that = this,
|
||||||
|
$selectableSearch = that.$selectableUl.prev(),
|
||||||
|
$selectionSearch = that.$selectionUl.prev(),
|
||||||
|
selectableSearchString = '#' + that.$container.attr('id') + ' .ms-elem-selectable:not(.ms-selected)',
|
||||||
|
selectionSearchString = '#' + that.$container.attr('id') + ' .ms-elem-selection.ms-selected';
|
||||||
|
|
||||||
|
that.qs1 = $selectableSearch.quicksearch(selectableSearchString)
|
||||||
|
.on('keydown', function (e) {
|
||||||
|
if (e.which === 40) {
|
||||||
|
that.$selectableUl.focus();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
that.qs2 = $selectionSearch.quicksearch(selectionSearchString)
|
||||||
|
.on('keydown', function (e) {
|
||||||
|
if (e.which == 40) {
|
||||||
|
that.$selectionUl.focus();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
afterSelect: function () {
|
||||||
|
this.qs1.cache();
|
||||||
|
this.qs2.cache();
|
||||||
|
},
|
||||||
|
afterDeselect: function () {
|
||||||
|
this.qs1.cache();
|
||||||
|
this.qs2.cache();
|
||||||
|
}
|
||||||
|
});
|
||||||
{% if plain_key %}
|
{% if plain_key %}
|
||||||
$(document.body).ready(function () {
|
$(document.body).ready(function () {
|
||||||
var modal = $("#modal_show_key");
|
var modal = $("#modal_show_key");
|
||||||
@ -165,4 +259,25 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- /.modal-dialog -->
|
<!-- /.modal-dialog -->
|
||||||
</div>
|
</div>
|
||||||
|
<div class="modal fade" id="modal_warning">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content modal-sm">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close" id="button_close_warn_modal">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
<h4 class="modal-title">WARNING</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p></p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-flat btn-primary center-block" id="button_key_confirm_warn">
|
||||||
|
OK</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- /.modal-content -->
|
||||||
|
</div>
|
||||||
|
<!-- /.modal-dialog -->
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -35,6 +35,7 @@
|
|||||||
<th>Role</th>
|
<th>Role</th>
|
||||||
<th>Description</th>
|
<th>Description</th>
|
||||||
<th>Domains</th>
|
<th>Domains</th>
|
||||||
|
<th>Accounts</th>
|
||||||
<th>Action</th>
|
<th>Action</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@ -45,6 +46,7 @@
|
|||||||
<td>{{ key.role.name }}</td>
|
<td>{{ key.role.name }}</td>
|
||||||
<td>{{ key.description }}</td>
|
<td>{{ key.description }}</td>
|
||||||
<td>{% for domain in key.domains %}{{ domain.name }}{% if not loop.last %}, {% endif %}{% endfor %}</td>
|
<td>{% for domain in key.domains %}{{ domain.name }}{% if not loop.last %}, {% endif %}{% endfor %}</td>
|
||||||
|
<td>{% for account in key.accounts %}{{ account.name }}{% if not loop.last %}, {% endif %}{% endfor %}</td>
|
||||||
<td width="15%">
|
<td width="15%">
|
||||||
<button type="button" class="btn btn-flat btn-success button_edit"
|
<button type="button" class="btn btn-flat btn-success button_edit"
|
||||||
onclick="window.location.href='{{ url_for('admin.edit_key', key_id=key.id) }}'">
|
onclick="window.location.href='{{ url_for('admin.edit_key', key_id=key.id) }}'">
|
||||||
|
1990
swagger-specv2.yaml
1990
swagger-specv2.yaml
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user