client+server: switch to yaml config
This commit is contained in:
		
							parent
							
								
									19a357611b
								
							
						
					
					
						commit
						55cc7b59e4
					
				
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@ -1,2 +1,2 @@
 | 
			
		||||
config.ini
 | 
			
		||||
config.yaml
 | 
			
		||||
*/*_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:
 | 
			
		||||
 | 
			
		||||
    ```console
 | 
			
		||||
    user@host:szuru$ cp config.ini.dist config.ini
 | 
			
		||||
    user@host:szuru$ vim config.ini
 | 
			
		||||
    user@host:szuru$ cp config.yaml.dist config.yaml
 | 
			
		||||
    user@host:szuru$ vim config.yaml
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
    Pay extra attention to the `[database]` section, `[smtp]` section, API URL
 | 
			
		||||
    and base URL in `[basic]`.
 | 
			
		||||
    Pay extra attention to API URL, base URL, the `database` section and the
 | 
			
		||||
    `smtp` section.
 | 
			
		||||
 | 
			
		||||
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.
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
@ -157,12 +157,11 @@ server {
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
**`config.ini`**:
 | 
			
		||||
**`config.yaml`**:
 | 
			
		||||
 | 
			
		||||
```ini
 | 
			
		||||
[basic]
 | 
			
		||||
api_url = http://big.dude/api/
 | 
			
		||||
base_url = http://big.dude/
 | 
			
		||||
```yaml
 | 
			
		||||
api_url: 'http://big.dude/api/'
 | 
			
		||||
base_url: 'http://big.dude/'
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Then the backend is started with `./server/host-waitress` from within
 | 
			
		||||
 | 
			
		||||
@ -5,40 +5,47 @@ const glob = require('glob');
 | 
			
		||||
const path = require('path');
 | 
			
		||||
const util = require('util');
 | 
			
		||||
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() {
 | 
			
		||||
    return execSync('git describe --always --dirty --long --tags').toString();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getConfig() {
 | 
			
		||||
    const ini = require('ini');
 | 
			
		||||
    const yaml = require('js-yaml');
 | 
			
		||||
    const merge = require('merge');
 | 
			
		||||
    const camelcaseKeys = require('camelcase-keys');
 | 
			
		||||
 | 
			
		||||
    function parseIniFile(path) {
 | 
			
		||||
        let result = ini.parse(fs.readFileSync(path, 'utf-8')
 | 
			
		||||
            .replace(/#.+$/gm, '')
 | 
			
		||||
            .replace(/\s+$/gm, ''));
 | 
			
		||||
        Object.keys(result).map((key, _) => {
 | 
			
		||||
            result[key] = camelcaseKeys(result[key]);
 | 
			
		||||
        });
 | 
			
		||||
        return result;
 | 
			
		||||
    function parseConfigFile(path) {
 | 
			
		||||
        let result = yaml.load(fs.readFileSync(path, 'utf-8'));
 | 
			
		||||
        return convertKeysToCamelCase(result);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let config = parseIniFile('../config.ini.dist');
 | 
			
		||||
    let config = parseConfigFile('../config.yaml.dist');
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
        const localConfig = parseIniFile('../config.ini');
 | 
			
		||||
        const localConfig = parseConfigFile('../config.yaml');
 | 
			
		||||
        config = merge.recursive(config, localConfig);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
        console.warn('Local config does not exist, ignoring');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    delete config.basic.secret;
 | 
			
		||||
    delete config.secret;
 | 
			
		||||
    delete config.smtp;
 | 
			
		||||
    delete config.database;
 | 
			
		||||
    config.service.userRanks = config.service.userRanks.split(/,\s*/);
 | 
			
		||||
    config.service.tagCategories = config.service.tagCategories.split(/,\s*/);
 | 
			
		||||
    config.meta = {
 | 
			
		||||
        version: getVersion(),
 | 
			
		||||
        buildDate: new Date().toUTCString(),
 | 
			
		||||
@ -63,7 +70,7 @@ function bundleHtml(config) {
 | 
			
		||||
            .replace(/(<\/head>)/, templatesHtml + '$1')
 | 
			
		||||
            .replace(
 | 
			
		||||
                /(<title>)(.*)(<\/title>)/,
 | 
			
		||||
                util.format('$1%s$3', config.basic.name));
 | 
			
		||||
                util.format('$1%s$3', config.name));
 | 
			
		||||
 | 
			
		||||
        fs.writeFileSync(
 | 
			
		||||
            './public/index.htm',
 | 
			
		||||
 | 
			
		||||
@ -48,7 +48,7 @@ class Api {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            const rankName = config.privileges[privilege];
 | 
			
		||||
            const rankIndex = config.service.userRanks.indexOf(rankName);
 | 
			
		||||
            const rankIndex = config.ranks.indexOf(rankName);
 | 
			
		||||
            if (minViableRank === null || rankIndex < minViableRank) {
 | 
			
		||||
                minViableRank = rankIndex;
 | 
			
		||||
            }
 | 
			
		||||
@ -57,7 +57,7 @@ class Api {
 | 
			
		||||
            console.error('Bad privilege name: ' + lookup);
 | 
			
		||||
        }
 | 
			
		||||
        let myRank = this.user !== null ?
 | 
			
		||||
            config.service.userRanks.indexOf(this.user.accessRank) :
 | 
			
		||||
            config.ranks.indexOf(this.user.rank) :
 | 
			
		||||
            0;
 | 
			
		||||
        return myRank >= minViableRank;
 | 
			
		||||
    }
 | 
			
		||||
@ -91,7 +91,7 @@ class Api {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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]({
 | 
			
		||||
            'name': config.basic.name,
 | 
			
		||||
            'name': config.name,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.showView(this.template({'content': content}));
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,7 @@ class HomeView extends BaseView {
 | 
			
		||||
 | 
			
		||||
    render(section) {
 | 
			
		||||
        this.showView(this.template({
 | 
			
		||||
            name: config.basic.name,
 | 
			
		||||
            name: config.name,
 | 
			
		||||
            version: config.meta.version,
 | 
			
		||||
            buildDate: config.meta.buildDate,
 | 
			
		||||
        }));
 | 
			
		||||
 | 
			
		||||
@ -17,8 +17,8 @@ class LoginView extends BaseView {
 | 
			
		||||
        const userNameField = document.getElementById('user-name');
 | 
			
		||||
        const passwordField = document.getElementById('user-password');
 | 
			
		||||
        const rememberUserField = document.getElementById('remember-user');
 | 
			
		||||
        userNameField.setAttribute('pattern', config.service.userNameRegex);
 | 
			
		||||
        passwordField.setAttribute('pattern', config.service.passwordRegex);
 | 
			
		||||
        userNameField.setAttribute('pattern', config.userNameRegex);
 | 
			
		||||
        passwordField.setAttribute('pattern', config.passwordRegex);
 | 
			
		||||
 | 
			
		||||
        form.addEventListener('submit', e => {
 | 
			
		||||
            e.preventDefault();
 | 
			
		||||
 | 
			
		||||
@ -17,8 +17,8 @@ class RegistrationView extends BaseView {
 | 
			
		||||
        const userNameField = document.getElementById('user-name');
 | 
			
		||||
        const passwordField = document.getElementById('user-password');
 | 
			
		||||
        const emailField = document.getElementById('user-email');
 | 
			
		||||
        userNameField.setAttribute('pattern', config.service.userNameRegex);
 | 
			
		||||
        passwordField.setAttribute('pattern', config.service.passwordRegex);
 | 
			
		||||
        userNameField.setAttribute('pattern', config.userNameRegex);
 | 
			
		||||
        passwordField.setAttribute('pattern', config.passwordRegex);
 | 
			
		||||
 | 
			
		||||
        form.addEventListener('submit', e => {
 | 
			
		||||
            e.preventDefault();
 | 
			
		||||
 | 
			
		||||
@ -7,13 +7,13 @@
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "browserify": "^13.0.0",
 | 
			
		||||
    "camelcase-keys": "^2.1.0",
 | 
			
		||||
    "camelcase": "^2.1.1",
 | 
			
		||||
    "csso": "^1.8.0",
 | 
			
		||||
    "glob": "^7.0.3",
 | 
			
		||||
    "handlebars": "^4.0.5",
 | 
			
		||||
    "html-minifier": "^1.3.1",
 | 
			
		||||
    "ini": "^1.3.4",
 | 
			
		||||
    "js-cookie": "^2.1.0",
 | 
			
		||||
    "js-yaml": "^3.5.5",
 | 
			
		||||
    "merge": "^1.2.0",
 | 
			
		||||
    "page": "^1.7.1",
 | 
			
		||||
    "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
 | 
			
		||||
configobj>=5.0.6
 | 
			
		||||
pyyaml>=3.11
 | 
			
		||||
falcon>=0.3.0
 | 
			
		||||
psycopg2>=2.6.1
 | 
			
		||||
SQLAlchemy>=1.0.12
 | 
			
		||||
 | 
			
		||||
@ -19,12 +19,12 @@ class PasswordResetApi(BaseApi):
 | 
			
		||||
                'User %r hasn\'t supplied email. Cannot reset password.' % user_name)
 | 
			
		||||
        token = auth.generate_authentication_token(user)
 | 
			
		||||
        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(
 | 
			
		||||
            'noreply@%s' % config.config['basic']['name'],
 | 
			
		||||
            'noreply@%s' % config.config['name'],
 | 
			
		||||
            user.email,
 | 
			
		||||
            MAIL_SUBJECT.format(name=config.config['basic']['name']),
 | 
			
		||||
            MAIL_BODY.format(name=config.config['basic']['name'], url=url))
 | 
			
		||||
            MAIL_SUBJECT.format(name=config.config['name']),
 | 
			
		||||
            MAIL_BODY.format(name=config.config['name'], url=url))
 | 
			
		||||
        return {}
 | 
			
		||||
 | 
			
		||||
    def post(self, context, user_name):
 | 
			
		||||
 | 
			
		||||
@ -1,13 +1,26 @@
 | 
			
		||||
import os
 | 
			
		||||
import configobj
 | 
			
		||||
import yaml
 | 
			
		||||
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):
 | 
			
		||||
    ''' INI config parser and container. '''
 | 
			
		||||
    ''' Config parser and container. '''
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        self.config = configobj.ConfigObj('../config.ini.dist')
 | 
			
		||||
        if os.path.exists('../config.ini'):
 | 
			
		||||
            self.config.merge(configobj.ConfigObj('../config.ini'))
 | 
			
		||||
        with open('../config.yaml.dist') as handle:
 | 
			
		||||
            self.config = yaml.load(handle.read())
 | 
			
		||||
        if os.path.exists('../config.yaml'):
 | 
			
		||||
            with open('../config.yaml') as handle:
 | 
			
		||||
                self.config = merge(self.config, yaml.load(handle.read()))
 | 
			
		||||
        self._validate()
 | 
			
		||||
 | 
			
		||||
    def __getitem__(self, key):
 | 
			
		||||
@ -15,22 +28,25 @@ class Config(object):
 | 
			
		||||
 | 
			
		||||
    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.
 | 
			
		||||
        '''
 | 
			
		||||
        all_ranks = self['service']['user_ranks']
 | 
			
		||||
        all_ranks = self['ranks']
 | 
			
		||||
        for privilege, rank in self['privileges'].items():
 | 
			
		||||
            if rank not in all_ranks:
 | 
			
		||||
                raise errors.ConfigError(
 | 
			
		||||
                    'Rank %r for privilege %r is missing from user_ranks' % (
 | 
			
		||||
                        rank, privilege))
 | 
			
		||||
                    'Rank %r for privilege %r is missing' % (rank, privilege))
 | 
			
		||||
        for rank in ['anonymous', 'admin', 'nobody']:
 | 
			
		||||
            if rank not in all_ranks:
 | 
			
		||||
                raise errors.ConfigError(
 | 
			
		||||
                    'Fixed rank %r is missing from user_ranks' % rank)
 | 
			
		||||
        if self['service']['default_user_rank'] not in all_ranks:
 | 
			
		||||
                raise errors.ConfigError('Protected rank %r is missing' % rank)
 | 
			
		||||
        if self['default_rank'] not in all_ranks:
 | 
			
		||||
            raise errors.ConfigError(
 | 
			
		||||
                'Default rank %r is missing from user_ranks' % (
 | 
			
		||||
                    self['service']['default_user_rank']))
 | 
			
		||||
                'Default rank %r is not on the list of known ranks' % (
 | 
			
		||||
                    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
 | 
			
		||||
 | 
			
		||||
@ -9,11 +9,9 @@ class TestPasswordReset(DatabaseTestCase):
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        super().setUp()
 | 
			
		||||
        config_mock = {
 | 
			
		||||
            'basic': {
 | 
			
		||||
                'secret': 'x',
 | 
			
		||||
                'base_url': 'http://example.com/',
 | 
			
		||||
                'name': 'Test instance',
 | 
			
		||||
            },
 | 
			
		||||
            'secret': 'x',
 | 
			
		||||
            'base_url': 'http://example.com/',
 | 
			
		||||
            'name': 'Test instance',
 | 
			
		||||
        }
 | 
			
		||||
        self.old_config = config.config
 | 
			
		||||
        config.config = config_mock
 | 
			
		||||
 | 
			
		||||
@ -13,9 +13,7 @@ class TestRetrievingUsers(DatabaseTestCase):
 | 
			
		||||
                'users:view': 'regular_user',
 | 
			
		||||
                'users:create': 'regular_user',
 | 
			
		||||
            },
 | 
			
		||||
            'service': {
 | 
			
		||||
                'user_ranks': ['anonymous', 'regular_user', 'mod', 'admin'],
 | 
			
		||||
            },
 | 
			
		||||
            'ranks': ['anonymous', 'regular_user', 'mod', 'admin'],
 | 
			
		||||
        }
 | 
			
		||||
        self.old_config = config.config
 | 
			
		||||
        config.config = config_mock
 | 
			
		||||
@ -74,15 +72,11 @@ class TestCreatingUser(DatabaseTestCase):
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        super().setUp()
 | 
			
		||||
        config_mock = {
 | 
			
		||||
            'basic': {
 | 
			
		||||
                'secret': '',
 | 
			
		||||
            },
 | 
			
		||||
            'service': {
 | 
			
		||||
                'user_name_regex': '.{3,}',
 | 
			
		||||
                'password_regex': '.{3,}',
 | 
			
		||||
                'user_ranks': ['anonymous', 'regular_user', 'mod', 'admin'],
 | 
			
		||||
                'default_user_rank': 'regular_user',
 | 
			
		||||
            },
 | 
			
		||||
            'secret': '',
 | 
			
		||||
            'user_name_regex': '.{3,}',
 | 
			
		||||
            'password_regex': '.{3,}',
 | 
			
		||||
            'default_rank': 'regular_user',
 | 
			
		||||
            'ranks': ['anonymous', 'regular_user', 'mod', 'admin'],
 | 
			
		||||
            'privileges': {
 | 
			
		||||
                'users:create': 'anonymous',
 | 
			
		||||
            },
 | 
			
		||||
@ -146,14 +140,10 @@ class TestUpdatingUser(DatabaseTestCase):
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        super().setUp()
 | 
			
		||||
        config_mock = {
 | 
			
		||||
            'basic': {
 | 
			
		||||
                'secret': '',
 | 
			
		||||
            },
 | 
			
		||||
            'service': {
 | 
			
		||||
                'user_name_regex': '.{3,}',
 | 
			
		||||
                'password_regex': '.{3,}',
 | 
			
		||||
                'user_ranks': ['anonymous', 'regular_user', 'mod', 'admin'],
 | 
			
		||||
            },
 | 
			
		||||
            'secret': '',
 | 
			
		||||
            'user_name_regex': '.{3,}',
 | 
			
		||||
            'password_regex': '.{3,}',
 | 
			
		||||
            'ranks': ['anonymous', 'regular_user', 'mod', 'admin'],
 | 
			
		||||
            'privileges': {
 | 
			
		||||
                'users:edit:self:name': 'regular_user',
 | 
			
		||||
                'users:edit:self:pass': 'regular_user',
 | 
			
		||||
 | 
			
		||||
@ -6,7 +6,7 @@ from szurubooru import errors
 | 
			
		||||
def get_password_hash(salt, password):
 | 
			
		||||
    ''' Retrieve new-style password hash. '''
 | 
			
		||||
    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(password.encode('utf8'))
 | 
			
		||||
    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.
 | 
			
		||||
    '''
 | 
			
		||||
    all_ranks = config.config['service']['user_ranks']
 | 
			
		||||
    all_ranks = config.config['ranks']
 | 
			
		||||
 | 
			
		||||
    assert privilege_name in config.config['privileges']
 | 
			
		||||
    assert user.rank in all_ranks
 | 
			
		||||
@ -54,6 +54,6 @@ def verify_privilege(user, privilege_name):
 | 
			
		||||
def generate_authentication_token(user):
 | 
			
		||||
    ''' Generate nonguessable challenge (e.g. links in password reminder). '''
 | 
			
		||||
    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'))
 | 
			
		||||
    return digest.hexdigest()
 | 
			
		||||
 | 
			
		||||
@ -10,7 +10,7 @@ def create_user(name, password, email):
 | 
			
		||||
    update_name(user, name)
 | 
			
		||||
    update_password(user, password)
 | 
			
		||||
    update_email(user, email)
 | 
			
		||||
    user.rank = config.config['service']['default_user_rank']
 | 
			
		||||
    user.rank = config.config['default_rank']
 | 
			
		||||
    user.creation_time = datetime.now()
 | 
			
		||||
    user.avatar_style = db.User.AVATAR_GRAVATAR
 | 
			
		||||
    return user
 | 
			
		||||
@ -18,7 +18,7 @@ def create_user(name, password, email):
 | 
			
		||||
def update_name(user, name):
 | 
			
		||||
    ''' Validate and update user's name. '''
 | 
			
		||||
    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):
 | 
			
		||||
        raise errors.ValidationError(
 | 
			
		||||
            'Name must satisfy regex %r.' % name_regex)
 | 
			
		||||
@ -26,7 +26,7 @@ def update_name(user, name):
 | 
			
		||||
 | 
			
		||||
def update_password(user, 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):
 | 
			
		||||
        raise errors.ValidationError(
 | 
			
		||||
            'Password must satisfy regex %r.' % password_regex)
 | 
			
		||||
@ -43,10 +43,10 @@ def update_email(user, email):
 | 
			
		||||
 | 
			
		||||
def update_rank(user, rank, authenticated_user):
 | 
			
		||||
    rank = rank.strip()
 | 
			
		||||
    available_ranks = config.config['service']['user_ranks']
 | 
			
		||||
    available_ranks = config.config['ranks']
 | 
			
		||||
    if not rank in available_ranks:
 | 
			
		||||
        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) \
 | 
			
		||||
            < available_ranks.index(rank):
 | 
			
		||||
        raise errors.AuthError('Trying to set higher rank than your own')
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user