client+server: switch to yaml config
This commit is contained in:
parent
19a357611b
commit
55cc7b59e4
18 changed files with 201 additions and 179 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,2 +1,2 @@
|
||||||
config.ini
|
config.yaml
|
||||||
*/*_modules/
|
*/*_modules/
|
||||||
|
|
19
INSTALL.md
19
INSTALL.md
|
@ -75,12 +75,12 @@ user@host:szuru/server$ source python_modules/bin/activate # enters the sandbox
|
||||||
1. Configure things:
|
1. Configure things:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
user@host:szuru$ cp config.ini.dist config.ini
|
user@host:szuru$ cp config.yaml.dist config.yaml
|
||||||
user@host:szuru$ vim config.ini
|
user@host:szuru$ vim config.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
Pay extra attention to the `[database]` section, `[smtp]` section, API URL
|
Pay extra attention to API URL, base URL, the `database` section and the
|
||||||
and base URL in `[basic]`.
|
`smtp` section.
|
||||||
|
|
||||||
2. Compile the frontend:
|
2. Compile the frontend:
|
||||||
|
|
||||||
|
@ -132,7 +132,7 @@ Below are described the methods to integrate the API into a web server:
|
||||||
`uwsgi`, but they'll need to write wrapper scripts themselves.
|
`uwsgi`, but they'll need to write wrapper scripts themselves.
|
||||||
|
|
||||||
Note that the API URL in the virtual host configuration needs to be the same as
|
Note that the API URL in the virtual host configuration needs to be the same as
|
||||||
the one in the `config.ini`, so that client knows how to access the backend!
|
the one in the `config.yaml`, so that client knows how to access the backend!
|
||||||
|
|
||||||
#### Example
|
#### Example
|
||||||
|
|
||||||
|
@ -157,12 +157,11 @@ server {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**`config.ini`**:
|
**`config.yaml`**:
|
||||||
|
|
||||||
```ini
|
```yaml
|
||||||
[basic]
|
api_url: 'http://big.dude/api/'
|
||||||
api_url = http://big.dude/api/
|
base_url: 'http://big.dude/'
|
||||||
base_url = http://big.dude/
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Then the backend is started with `./server/host-waitress` from within
|
Then the backend is started with `./server/host-waitress` from within
|
||||||
|
|
|
@ -5,40 +5,47 @@ const glob = require('glob');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const util = require('util');
|
const util = require('util');
|
||||||
const execSync = require('child_process').execSync;
|
const execSync = require('child_process').execSync;
|
||||||
|
const camelcase = require('camelcase');
|
||||||
|
|
||||||
|
function convertKeysToCamelCase(input) {
|
||||||
|
let result = {};
|
||||||
|
Object.keys(input).map((key, _) => {
|
||||||
|
const value = input[key];
|
||||||
|
if (value !== null && value.constructor == Object) {
|
||||||
|
result[camelcase(key)] = convertKeysToCamelCase(value);
|
||||||
|
} else {
|
||||||
|
result[camelcase(key)] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
function getVersion() {
|
function getVersion() {
|
||||||
return execSync('git describe --always --dirty --long --tags').toString();
|
return execSync('git describe --always --dirty --long --tags').toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getConfig() {
|
function getConfig() {
|
||||||
const ini = require('ini');
|
const yaml = require('js-yaml');
|
||||||
const merge = require('merge');
|
const merge = require('merge');
|
||||||
const camelcaseKeys = require('camelcase-keys');
|
const camelcaseKeys = require('camelcase-keys');
|
||||||
|
|
||||||
function parseIniFile(path) {
|
function parseConfigFile(path) {
|
||||||
let result = ini.parse(fs.readFileSync(path, 'utf-8')
|
let result = yaml.load(fs.readFileSync(path, 'utf-8'));
|
||||||
.replace(/#.+$/gm, '')
|
return convertKeysToCamelCase(result);
|
||||||
.replace(/\s+$/gm, ''));
|
|
||||||
Object.keys(result).map((key, _) => {
|
|
||||||
result[key] = camelcaseKeys(result[key]);
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let config = parseIniFile('../config.ini.dist');
|
let config = parseConfigFile('../config.yaml.dist');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const localConfig = parseIniFile('../config.ini');
|
const localConfig = parseConfigFile('../config.yaml');
|
||||||
config = merge.recursive(config, localConfig);
|
config = merge.recursive(config, localConfig);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('Local config does not exist, ignoring');
|
console.warn('Local config does not exist, ignoring');
|
||||||
}
|
}
|
||||||
|
|
||||||
delete config.basic.secret;
|
delete config.secret;
|
||||||
delete config.smtp;
|
delete config.smtp;
|
||||||
delete config.database;
|
delete config.database;
|
||||||
config.service.userRanks = config.service.userRanks.split(/,\s*/);
|
|
||||||
config.service.tagCategories = config.service.tagCategories.split(/,\s*/);
|
|
||||||
config.meta = {
|
config.meta = {
|
||||||
version: getVersion(),
|
version: getVersion(),
|
||||||
buildDate: new Date().toUTCString(),
|
buildDate: new Date().toUTCString(),
|
||||||
|
@ -63,7 +70,7 @@ function bundleHtml(config) {
|
||||||
.replace(/(<\/head>)/, templatesHtml + '$1')
|
.replace(/(<\/head>)/, templatesHtml + '$1')
|
||||||
.replace(
|
.replace(
|
||||||
/(<title>)(.*)(<\/title>)/,
|
/(<title>)(.*)(<\/title>)/,
|
||||||
util.format('$1%s$3', config.basic.name));
|
util.format('$1%s$3', config.name));
|
||||||
|
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
'./public/index.htm',
|
'./public/index.htm',
|
||||||
|
|
|
@ -48,7 +48,7 @@ class Api {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const rankName = config.privileges[privilege];
|
const rankName = config.privileges[privilege];
|
||||||
const rankIndex = config.service.userRanks.indexOf(rankName);
|
const rankIndex = config.ranks.indexOf(rankName);
|
||||||
if (minViableRank === null || rankIndex < minViableRank) {
|
if (minViableRank === null || rankIndex < minViableRank) {
|
||||||
minViableRank = rankIndex;
|
minViableRank = rankIndex;
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,7 @@ class Api {
|
||||||
console.error('Bad privilege name: ' + lookup);
|
console.error('Bad privilege name: ' + lookup);
|
||||||
}
|
}
|
||||||
let myRank = this.user !== null ?
|
let myRank = this.user !== null ?
|
||||||
config.service.userRanks.indexOf(this.user.accessRank) :
|
config.ranks.indexOf(this.user.rank) :
|
||||||
0;
|
0;
|
||||||
return myRank >= minViableRank;
|
return myRank >= minViableRank;
|
||||||
}
|
}
|
||||||
|
@ -91,7 +91,7 @@ class Api {
|
||||||
}
|
}
|
||||||
|
|
||||||
getFullUrl(url) {
|
getFullUrl(url) {
|
||||||
return (config.basic.apiUrl + '/' + url).replace(/([^:])\/+/g, '$1/');
|
return (config.apiUrl + '/' + url).replace(/([^:])\/+/g, '$1/');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ class HelpView extends BaseView {
|
||||||
}
|
}
|
||||||
|
|
||||||
const content = this.sectionTemplates[section]({
|
const content = this.sectionTemplates[section]({
|
||||||
'name': config.basic.name,
|
'name': config.name,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.showView(this.template({'content': content}));
|
this.showView(this.template({'content': content}));
|
||||||
|
|
|
@ -11,7 +11,7 @@ class HomeView extends BaseView {
|
||||||
|
|
||||||
render(section) {
|
render(section) {
|
||||||
this.showView(this.template({
|
this.showView(this.template({
|
||||||
name: config.basic.name,
|
name: config.name,
|
||||||
version: config.meta.version,
|
version: config.meta.version,
|
||||||
buildDate: config.meta.buildDate,
|
buildDate: config.meta.buildDate,
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -17,8 +17,8 @@ class LoginView extends BaseView {
|
||||||
const userNameField = document.getElementById('user-name');
|
const userNameField = document.getElementById('user-name');
|
||||||
const passwordField = document.getElementById('user-password');
|
const passwordField = document.getElementById('user-password');
|
||||||
const rememberUserField = document.getElementById('remember-user');
|
const rememberUserField = document.getElementById('remember-user');
|
||||||
userNameField.setAttribute('pattern', config.service.userNameRegex);
|
userNameField.setAttribute('pattern', config.userNameRegex);
|
||||||
passwordField.setAttribute('pattern', config.service.passwordRegex);
|
passwordField.setAttribute('pattern', config.passwordRegex);
|
||||||
|
|
||||||
form.addEventListener('submit', e => {
|
form.addEventListener('submit', e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
|
@ -17,8 +17,8 @@ class RegistrationView extends BaseView {
|
||||||
const userNameField = document.getElementById('user-name');
|
const userNameField = document.getElementById('user-name');
|
||||||
const passwordField = document.getElementById('user-password');
|
const passwordField = document.getElementById('user-password');
|
||||||
const emailField = document.getElementById('user-email');
|
const emailField = document.getElementById('user-email');
|
||||||
userNameField.setAttribute('pattern', config.service.userNameRegex);
|
userNameField.setAttribute('pattern', config.userNameRegex);
|
||||||
passwordField.setAttribute('pattern', config.service.passwordRegex);
|
passwordField.setAttribute('pattern', config.passwordRegex);
|
||||||
|
|
||||||
form.addEventListener('submit', e => {
|
form.addEventListener('submit', e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
|
@ -7,13 +7,13 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"browserify": "^13.0.0",
|
"browserify": "^13.0.0",
|
||||||
"camelcase-keys": "^2.1.0",
|
"camelcase": "^2.1.1",
|
||||||
"csso": "^1.8.0",
|
"csso": "^1.8.0",
|
||||||
"glob": "^7.0.3",
|
"glob": "^7.0.3",
|
||||||
"handlebars": "^4.0.5",
|
"handlebars": "^4.0.5",
|
||||||
"html-minifier": "^1.3.1",
|
"html-minifier": "^1.3.1",
|
||||||
"ini": "^1.3.4",
|
|
||||||
"js-cookie": "^2.1.0",
|
"js-cookie": "^2.1.0",
|
||||||
|
"js-yaml": "^3.5.5",
|
||||||
"merge": "^1.2.0",
|
"merge": "^1.2.0",
|
||||||
"page": "^1.7.1",
|
"page": "^1.7.1",
|
||||||
"superagent": "^1.8.3",
|
"superagent": "^1.8.3",
|
||||||
|
|
|
@ -1,90 +0,0 @@
|
||||||
# rather than editing this file, it is strongly suggested to create config.ini
|
|
||||||
# and override only what you need.
|
|
||||||
|
|
||||||
[basic]
|
|
||||||
name = szurubooru
|
|
||||||
debug = 0
|
|
||||||
secret = change
|
|
||||||
api_url = "http://api.example.com/" # where frontend connects to
|
|
||||||
base_url = "http://example.com/" # used in absolute links (e.g. password reminder)
|
|
||||||
|
|
||||||
[database]
|
|
||||||
schema = postgres
|
|
||||||
host = localhost
|
|
||||||
port = 5432
|
|
||||||
user = szuru
|
|
||||||
pass = dog
|
|
||||||
name = szuru
|
|
||||||
|
|
||||||
[smtp]
|
|
||||||
# used to send password reminders
|
|
||||||
host = localhost
|
|
||||||
port = 25
|
|
||||||
user = bot
|
|
||||||
pass = groovy123
|
|
||||||
|
|
||||||
[service]
|
|
||||||
user_ranks = anonymous, regular_user, power_user, mod, admin, nobody
|
|
||||||
default_user_rank = regular_user
|
|
||||||
users_per_page = 20
|
|
||||||
posts_per_page = 40
|
|
||||||
max_comment_length = 5000
|
|
||||||
tag_categories = meta, artist, character, copyright, other unique
|
|
||||||
|
|
||||||
# don't change these regexes, unless you want to annoy people. but if you do
|
|
||||||
# customize them, make sure to update the instructions in the registration form
|
|
||||||
# template as well.
|
|
||||||
password_regex = "^.{5,}$"
|
|
||||||
user_name_regex = "^[a-zA-Z0-9_-]{1,32}$"
|
|
||||||
|
|
||||||
[privileges]
|
|
||||||
users:create = anonymous
|
|
||||||
users:list = regular_user
|
|
||||||
users:view = regular_user
|
|
||||||
users:edit:any:name = mod
|
|
||||||
users:edit:any:pass = mod
|
|
||||||
users:edit:any:email = mod
|
|
||||||
users:edit:any:avatar = mod
|
|
||||||
# note: promoting people to higher rank than one's own is always impossible
|
|
||||||
users:edit:any:rank = mod
|
|
||||||
users:edit:self:name = regular_user
|
|
||||||
users:edit:self:pass = regular_user
|
|
||||||
users:edit:self:email = regular_user
|
|
||||||
users:edit:self:avatar = regular_user
|
|
||||||
users:edit:self:rank = mod
|
|
||||||
users:delete:any = admin
|
|
||||||
users:delete:self = regular_user
|
|
||||||
|
|
||||||
posts:create:anonymous = regular_user
|
|
||||||
posts:create:identified = regular_user
|
|
||||||
posts:list = anonymous
|
|
||||||
posts:view = anonymous
|
|
||||||
posts:edit:content = power_user
|
|
||||||
posts:edit:flags = regular_user
|
|
||||||
posts:edit:notes = regular_user
|
|
||||||
posts:edit:relations = regular_user
|
|
||||||
posts:edit:safety = power_user
|
|
||||||
posts:edit:source = regular_user
|
|
||||||
posts:edit:tags = regular_user
|
|
||||||
posts:edit:thumbnail = power_user
|
|
||||||
posts:feature = mod
|
|
||||||
posts:delete = mod
|
|
||||||
|
|
||||||
tags:create = regular_user
|
|
||||||
tags:edit:name = power_user
|
|
||||||
tags:edit:category = power_user
|
|
||||||
tags:edit:implications = power_user
|
|
||||||
tags:edit:suggestions = power_user
|
|
||||||
tags:list = regular_user
|
|
||||||
tags:masstag = power_user
|
|
||||||
tags:merge = mod
|
|
||||||
tags:delete = mod
|
|
||||||
|
|
||||||
comments:create = regular_user
|
|
||||||
comments:delete:any = mod
|
|
||||||
comments:delete:own = regular_user
|
|
||||||
comments:edit:any = mod
|
|
||||||
comments:edit:own = regular_user
|
|
||||||
comments:list = regular_user
|
|
||||||
|
|
||||||
history:view = power_user
|
|
102
config.yaml.dist
Normal file
102
config.yaml.dist
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
# rather than editing this file, it is strongly suggested to create config.ini
|
||||||
|
# and override only what you need.
|
||||||
|
|
||||||
|
name: szurubooru
|
||||||
|
debug: 0
|
||||||
|
secret: change
|
||||||
|
api_url: # where frontend connects to, example: http://api.example.com/
|
||||||
|
base_url: # used to form absolute links, example: http://example.com/
|
||||||
|
|
||||||
|
database:
|
||||||
|
schema: postgres
|
||||||
|
host: # example: localhost
|
||||||
|
port: # example: 5432
|
||||||
|
user: # example: szuru
|
||||||
|
pass: # example: dog
|
||||||
|
name: # example: szuru
|
||||||
|
|
||||||
|
# used to send password reminders
|
||||||
|
smtp:
|
||||||
|
host: # example: localhost
|
||||||
|
port: # example: 25
|
||||||
|
user: # example: bot
|
||||||
|
pass: # example: groovy123
|
||||||
|
|
||||||
|
limits:
|
||||||
|
users_per_page: 20
|
||||||
|
posts_per_page: 40
|
||||||
|
max_comment_length: 5000
|
||||||
|
|
||||||
|
tag_categories:
|
||||||
|
- meta
|
||||||
|
- artist
|
||||||
|
- character
|
||||||
|
- copyright
|
||||||
|
- other unique
|
||||||
|
|
||||||
|
# changing ranks after deployment may require manual tweaks to the database.
|
||||||
|
ranks:
|
||||||
|
- anonymous
|
||||||
|
- regular_user
|
||||||
|
- power_user
|
||||||
|
- mod
|
||||||
|
- admin
|
||||||
|
- nobody
|
||||||
|
default_rank: regular_user
|
||||||
|
|
||||||
|
# don't change these, unless you want to annoy people. if you do customize
|
||||||
|
# them though, make sure to update the instructions in the registration form
|
||||||
|
# template as well.
|
||||||
|
password_regex: '^.{5,}$'
|
||||||
|
user_name_regex: '^[a-zA-Z0-9_-]{1,32}$'
|
||||||
|
|
||||||
|
privileges:
|
||||||
|
'users:create': anonymous
|
||||||
|
'users:list': regular_user
|
||||||
|
'users:view': regular_user
|
||||||
|
'users:edit:any:name': mod
|
||||||
|
'users:edit:any:pass': mod
|
||||||
|
'users:edit:any:email': mod
|
||||||
|
'users:edit:any:avatar': mod
|
||||||
|
'users:edit:any:rank': mod
|
||||||
|
'users:edit:self:name': regular_user
|
||||||
|
'users:edit:self:pass': regular_user
|
||||||
|
'users:edit:self:email': regular_user
|
||||||
|
'users:edit:self:avatar': regular_user
|
||||||
|
'users:edit:self:rank': mod # one can't promote themselves or anyone to upper rank than their own.
|
||||||
|
'users:delete:any': admin
|
||||||
|
'users:delete:self': regular_user
|
||||||
|
|
||||||
|
'posts:create:anonymous': regular_user
|
||||||
|
'posts:create:identified': regular_user
|
||||||
|
'posts:list': anonymous
|
||||||
|
'posts:view': anonymous
|
||||||
|
'posts:edit:content': power_user
|
||||||
|
'posts:edit:flags': regular_user
|
||||||
|
'posts:edit:notes': regular_user
|
||||||
|
'posts:edit:relations': regular_user
|
||||||
|
'posts:edit:safety': power_user
|
||||||
|
'posts:edit:source': regular_user
|
||||||
|
'posts:edit:tags': regular_user
|
||||||
|
'posts:edit:thumbnail': power_user
|
||||||
|
'posts:feature': mod
|
||||||
|
'posts:delete': mod
|
||||||
|
|
||||||
|
'tags:create': regular_user
|
||||||
|
'tags:edit:name': power_user
|
||||||
|
'tags:edit:category': power_user
|
||||||
|
'tags:edit:implications': power_user
|
||||||
|
'tags:edit:suggestions': power_user
|
||||||
|
'tags:list': regular_user
|
||||||
|
'tags:masstag': power_user
|
||||||
|
'tags:merge': mod
|
||||||
|
'tags:delete': mod
|
||||||
|
|
||||||
|
'comments:create': regular_user
|
||||||
|
'comments:delete:any': mod
|
||||||
|
'comments:delete:own': regular_user
|
||||||
|
'comments:edit:any': mod
|
||||||
|
'comments:edit:own': regular_user
|
||||||
|
'comments:list': regular_user
|
||||||
|
|
||||||
|
'history:view': power_user
|
|
@ -1,5 +1,5 @@
|
||||||
alembic>=0.8.5
|
alembic>=0.8.5
|
||||||
configobj>=5.0.6
|
pyyaml>=3.11
|
||||||
falcon>=0.3.0
|
falcon>=0.3.0
|
||||||
psycopg2>=2.6.1
|
psycopg2>=2.6.1
|
||||||
SQLAlchemy>=1.0.12
|
SQLAlchemy>=1.0.12
|
||||||
|
|
|
@ -19,12 +19,12 @@ class PasswordResetApi(BaseApi):
|
||||||
'User %r hasn\'t supplied email. Cannot reset password.' % user_name)
|
'User %r hasn\'t supplied email. Cannot reset password.' % user_name)
|
||||||
token = auth.generate_authentication_token(user)
|
token = auth.generate_authentication_token(user)
|
||||||
url = '%s/password-reset/%s:%s' % (
|
url = '%s/password-reset/%s:%s' % (
|
||||||
config.config['basic']['base_url'].rstrip('/'), user.name, token)
|
config.config['base_url'].rstrip('/'), user.name, token)
|
||||||
mailer.send_mail(
|
mailer.send_mail(
|
||||||
'noreply@%s' % config.config['basic']['name'],
|
'noreply@%s' % config.config['name'],
|
||||||
user.email,
|
user.email,
|
||||||
MAIL_SUBJECT.format(name=config.config['basic']['name']),
|
MAIL_SUBJECT.format(name=config.config['name']),
|
||||||
MAIL_BODY.format(name=config.config['basic']['name'], url=url))
|
MAIL_BODY.format(name=config.config['name'], url=url))
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def post(self, context, user_name):
|
def post(self, context, user_name):
|
||||||
|
|
|
@ -1,13 +1,26 @@
|
||||||
import os
|
import os
|
||||||
import configobj
|
import yaml
|
||||||
from szurubooru import errors
|
from szurubooru import errors
|
||||||
|
|
||||||
|
def merge(left, right):
|
||||||
|
for key in right:
|
||||||
|
if key in left:
|
||||||
|
if isinstance(left[key], dict) and isinstance(right[key], dict):
|
||||||
|
merge(left[key], right[key])
|
||||||
|
elif left[key] != right[key]:
|
||||||
|
left[key] = right[key]
|
||||||
|
else:
|
||||||
|
left[key] = right[key]
|
||||||
|
return left
|
||||||
|
|
||||||
class Config(object):
|
class Config(object):
|
||||||
''' INI config parser and container. '''
|
''' Config parser and container. '''
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.config = configobj.ConfigObj('../config.ini.dist')
|
with open('../config.yaml.dist') as handle:
|
||||||
if os.path.exists('../config.ini'):
|
self.config = yaml.load(handle.read())
|
||||||
self.config.merge(configobj.ConfigObj('../config.ini'))
|
if os.path.exists('../config.yaml'):
|
||||||
|
with open('../config.yaml') as handle:
|
||||||
|
self.config = merge(self.config, yaml.load(handle.read()))
|
||||||
self._validate()
|
self._validate()
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
|
@ -15,22 +28,25 @@ class Config(object):
|
||||||
|
|
||||||
def _validate(self):
|
def _validate(self):
|
||||||
'''
|
'''
|
||||||
Check whether config.ini doesn't contain errors that might prove
|
Check whether config doesn't contain errors that might prove
|
||||||
lethal at runtime.
|
lethal at runtime.
|
||||||
'''
|
'''
|
||||||
all_ranks = self['service']['user_ranks']
|
all_ranks = self['ranks']
|
||||||
for privilege, rank in self['privileges'].items():
|
for privilege, rank in self['privileges'].items():
|
||||||
if rank not in all_ranks:
|
if rank not in all_ranks:
|
||||||
raise errors.ConfigError(
|
raise errors.ConfigError(
|
||||||
'Rank %r for privilege %r is missing from user_ranks' % (
|
'Rank %r for privilege %r is missing' % (rank, privilege))
|
||||||
rank, privilege))
|
|
||||||
for rank in ['anonymous', 'admin', 'nobody']:
|
for rank in ['anonymous', 'admin', 'nobody']:
|
||||||
if rank not in all_ranks:
|
if rank not in all_ranks:
|
||||||
raise errors.ConfigError(
|
raise errors.ConfigError('Protected rank %r is missing' % rank)
|
||||||
'Fixed rank %r is missing from user_ranks' % rank)
|
if self['default_rank'] not in all_ranks:
|
||||||
if self['service']['default_user_rank'] not in all_ranks:
|
|
||||||
raise errors.ConfigError(
|
raise errors.ConfigError(
|
||||||
'Default rank %r is missing from user_ranks' % (
|
'Default rank %r is not on the list of known ranks' % (
|
||||||
self['service']['default_user_rank']))
|
self['default_rank']))
|
||||||
|
|
||||||
|
for key in ['schema', 'host', 'port', 'user', 'pass', 'name']:
|
||||||
|
if not self['database'][key]:
|
||||||
|
raise errors.ConfigError(
|
||||||
|
'Database is not configured: %r is missing' % key)
|
||||||
|
|
||||||
config = Config() # pylint: disable=invalid-name
|
config = Config() # pylint: disable=invalid-name
|
||||||
|
|
|
@ -9,11 +9,9 @@ class TestPasswordReset(DatabaseTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
config_mock = {
|
config_mock = {
|
||||||
'basic': {
|
'secret': 'x',
|
||||||
'secret': 'x',
|
'base_url': 'http://example.com/',
|
||||||
'base_url': 'http://example.com/',
|
'name': 'Test instance',
|
||||||
'name': 'Test instance',
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
self.old_config = config.config
|
self.old_config = config.config
|
||||||
config.config = config_mock
|
config.config = config_mock
|
||||||
|
|
|
@ -13,9 +13,7 @@ class TestRetrievingUsers(DatabaseTestCase):
|
||||||
'users:view': 'regular_user',
|
'users:view': 'regular_user',
|
||||||
'users:create': 'regular_user',
|
'users:create': 'regular_user',
|
||||||
},
|
},
|
||||||
'service': {
|
'ranks': ['anonymous', 'regular_user', 'mod', 'admin'],
|
||||||
'user_ranks': ['anonymous', 'regular_user', 'mod', 'admin'],
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
self.old_config = config.config
|
self.old_config = config.config
|
||||||
config.config = config_mock
|
config.config = config_mock
|
||||||
|
@ -74,15 +72,11 @@ class TestCreatingUser(DatabaseTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
config_mock = {
|
config_mock = {
|
||||||
'basic': {
|
'secret': '',
|
||||||
'secret': '',
|
'user_name_regex': '.{3,}',
|
||||||
},
|
'password_regex': '.{3,}',
|
||||||
'service': {
|
'default_rank': 'regular_user',
|
||||||
'user_name_regex': '.{3,}',
|
'ranks': ['anonymous', 'regular_user', 'mod', 'admin'],
|
||||||
'password_regex': '.{3,}',
|
|
||||||
'user_ranks': ['anonymous', 'regular_user', 'mod', 'admin'],
|
|
||||||
'default_user_rank': 'regular_user',
|
|
||||||
},
|
|
||||||
'privileges': {
|
'privileges': {
|
||||||
'users:create': 'anonymous',
|
'users:create': 'anonymous',
|
||||||
},
|
},
|
||||||
|
@ -146,14 +140,10 @@ class TestUpdatingUser(DatabaseTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
config_mock = {
|
config_mock = {
|
||||||
'basic': {
|
'secret': '',
|
||||||
'secret': '',
|
'user_name_regex': '.{3,}',
|
||||||
},
|
'password_regex': '.{3,}',
|
||||||
'service': {
|
'ranks': ['anonymous', 'regular_user', 'mod', 'admin'],
|
||||||
'user_name_regex': '.{3,}',
|
|
||||||
'password_regex': '.{3,}',
|
|
||||||
'user_ranks': ['anonymous', 'regular_user', 'mod', 'admin'],
|
|
||||||
},
|
|
||||||
'privileges': {
|
'privileges': {
|
||||||
'users:edit:self:name': 'regular_user',
|
'users:edit:self:name': 'regular_user',
|
||||||
'users:edit:self:pass': 'regular_user',
|
'users:edit:self:pass': 'regular_user',
|
||||||
|
|
|
@ -6,7 +6,7 @@ from szurubooru import errors
|
||||||
def get_password_hash(salt, password):
|
def get_password_hash(salt, password):
|
||||||
''' Retrieve new-style password hash. '''
|
''' Retrieve new-style password hash. '''
|
||||||
digest = hashlib.sha256()
|
digest = hashlib.sha256()
|
||||||
digest.update(config.config['basic']['secret'].encode('utf8'))
|
digest.update(config.config['secret'].encode('utf8'))
|
||||||
digest.update(salt.encode('utf8'))
|
digest.update(salt.encode('utf8'))
|
||||||
digest.update(password.encode('utf8'))
|
digest.update(password.encode('utf8'))
|
||||||
return digest.hexdigest()
|
return digest.hexdigest()
|
||||||
|
@ -42,7 +42,7 @@ def verify_privilege(user, privilege_name):
|
||||||
'''
|
'''
|
||||||
Throw an AuthError if the given user doesn't have given privilege.
|
Throw an AuthError if the given user doesn't have given privilege.
|
||||||
'''
|
'''
|
||||||
all_ranks = config.config['service']['user_ranks']
|
all_ranks = config.config['ranks']
|
||||||
|
|
||||||
assert privilege_name in config.config['privileges']
|
assert privilege_name in config.config['privileges']
|
||||||
assert user.rank in all_ranks
|
assert user.rank in all_ranks
|
||||||
|
@ -54,6 +54,6 @@ def verify_privilege(user, privilege_name):
|
||||||
def generate_authentication_token(user):
|
def generate_authentication_token(user):
|
||||||
''' Generate nonguessable challenge (e.g. links in password reminder). '''
|
''' Generate nonguessable challenge (e.g. links in password reminder). '''
|
||||||
digest = hashlib.md5()
|
digest = hashlib.md5()
|
||||||
digest.update(config.config['basic']['secret'].encode('utf8'))
|
digest.update(config.config['secret'].encode('utf8'))
|
||||||
digest.update(user.password_salt.encode('utf8'))
|
digest.update(user.password_salt.encode('utf8'))
|
||||||
return digest.hexdigest()
|
return digest.hexdigest()
|
||||||
|
|
|
@ -10,7 +10,7 @@ def create_user(name, password, email):
|
||||||
update_name(user, name)
|
update_name(user, name)
|
||||||
update_password(user, password)
|
update_password(user, password)
|
||||||
update_email(user, email)
|
update_email(user, email)
|
||||||
user.rank = config.config['service']['default_user_rank']
|
user.rank = config.config['default_rank']
|
||||||
user.creation_time = datetime.now()
|
user.creation_time = datetime.now()
|
||||||
user.avatar_style = db.User.AVATAR_GRAVATAR
|
user.avatar_style = db.User.AVATAR_GRAVATAR
|
||||||
return user
|
return user
|
||||||
|
@ -18,7 +18,7 @@ def create_user(name, password, email):
|
||||||
def update_name(user, name):
|
def update_name(user, name):
|
||||||
''' Validate and update user's name. '''
|
''' Validate and update user's name. '''
|
||||||
name = name.strip()
|
name = name.strip()
|
||||||
name_regex = config.config['service']['user_name_regex']
|
name_regex = config.config['user_name_regex']
|
||||||
if not re.match(name_regex, name):
|
if not re.match(name_regex, name):
|
||||||
raise errors.ValidationError(
|
raise errors.ValidationError(
|
||||||
'Name must satisfy regex %r.' % name_regex)
|
'Name must satisfy regex %r.' % name_regex)
|
||||||
|
@ -26,7 +26,7 @@ def update_name(user, name):
|
||||||
|
|
||||||
def update_password(user, password):
|
def update_password(user, password):
|
||||||
''' Validate and update user's password. '''
|
''' Validate and update user's password. '''
|
||||||
password_regex = config.config['service']['password_regex']
|
password_regex = config.config['password_regex']
|
||||||
if not re.match(password_regex, password):
|
if not re.match(password_regex, password):
|
||||||
raise errors.ValidationError(
|
raise errors.ValidationError(
|
||||||
'Password must satisfy regex %r.' % password_regex)
|
'Password must satisfy regex %r.' % password_regex)
|
||||||
|
@ -43,10 +43,10 @@ def update_email(user, email):
|
||||||
|
|
||||||
def update_rank(user, rank, authenticated_user):
|
def update_rank(user, rank, authenticated_user):
|
||||||
rank = rank.strip()
|
rank = rank.strip()
|
||||||
available_ranks = config.config['service']['user_ranks']
|
available_ranks = config.config['ranks']
|
||||||
if not rank in available_ranks:
|
if not rank in available_ranks:
|
||||||
raise errors.ValidationError(
|
raise errors.ValidationError(
|
||||||
'Bad rank. Valid ranks: %r' % available_ranks)
|
'Bad rank %r. Valid ranks: %r' % (rank, available_ranks))
|
||||||
if available_ranks.index(authenticated_user.rank) \
|
if available_ranks.index(authenticated_user.rank) \
|
||||||
< available_ranks.index(rank):
|
< available_ranks.index(rank):
|
||||||
raise errors.AuthError('Trying to set higher rank than your own')
|
raise errors.AuthError('Trying to set higher rank than your own')
|
||||||
|
|
Loading…
Reference in a new issue