Compare commits

..

No commits in common. "master" and "use-python-fire" have entirely different histories.

13 changed files with 42 additions and 387 deletions

View File

@ -1,12 +0,0 @@
Copyright (C) 2019 Kopano and its licensors
This program is free software: you can redistribute it and/or modify it under the terms of the
GNU Affero General Public License as published by the Free Software Foundation, either version
3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along with this program.
If not, see http://www.gnu.org/licenses/.

View File

@ -1,27 +0,0 @@
# WebApp tools
WebApp tools repository can be found [here](https://stash.kopano.io/projects/KSC/repos/webapp-tools/) and consist of various sub-projects:
## WebApp_Admin
A CLI tool that can be used to manage WebApp user settings, signatures, S/MIME certificates and more.
More info [here](https://stash.kopano.io/projects/KSC/repos/webapp-tools/browse/webapp_admin/README.md).
## Files_Admin
A CLI tool to inject files accounts into a user settings. More info [here](https://stash.kopano.io/projects/KSC/repos/webapp-tools/browse/files_admin/README.md).
## Manage_recipients
A simple script to manage the recipient history list for a user. More info [here](https://stash.kopano.io/projects/KSC/repos/webapp-tools/browse/manage_recipients/readme.md).
# How to contribute
1) Clone the repository from `https://stash.kopano.io/` or `https://github.com/Kopano-mirror/webapp_tools`.
2) Commit and sign your work (git commit -s).
3) Upload commits to a git store of your choosing, or export the series as a patchset using git format-patch.
4) Send the patch(es) or git link to `contributing @ kopano .io` and we will consider the submission.
# License
All software found in this repostory and sub-projects are licensed under GNU Affero General Public License v3 unless stated otherwise. A copy of this license can be found in the main directory of this project and in every sub-project.

View File

@ -1,12 +0,0 @@
Copyright (C) 2019 Kopano and its licensors
This program is free software: you can redistribute it and/or modify it under the terms of the
GNU Affero General Public License as published by the Free Software Foundation, either version
3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along with this program.
If not, see http://www.gnu.org/licenses/.

View File

@ -20,7 +20,3 @@ Use the username and password provided in the config file
- python-kopano
- python-mapi
# License
licensed under GNU Affero General Public License v3.

View File

@ -54,19 +54,11 @@ def files(options):
files = options.file.split(',')
for file in files:
configfile = ConfigObj(file)
# Check if the settings section key is present in the file
try:
value = configfile['setting']
except KeyError:
print('Setting does not exist in', file)
continue
if configfile['setting'].as_bool('use_zarafa_credentials'):
username = options.user
if configfile['setting']['use_zarafa_credentials']:
username = configfile['setting']['default_user']
else:
username = configfile['setting'].get('default_user', 'does not matter')
password = encode(configfile['setting'].get('default_password', 'does not matter'))
username = options.user
password = encode(configfile['setting']['default_password'])
backendoptions = {
'ftp': {"backend_features": {
"Streaming": "true", }},
@ -101,7 +93,6 @@ def files(options):
address = configfile['setting']['server_address']
ssl = configfile['setting']['server_ssl']
id = str(uuid.uuid4())
filesjson['accounts'][id] = {
"status": "ok",
"backend_config": {
@ -110,7 +101,7 @@ def files(options):
"server_address": encode(address).decode('utf-8'),
"server_ssl": ssl,
"current_account_id": encode('d4cacda458a2a26c301f2b7d75ada530').decode('utf-8'),
"use_zarafa_credentials": configfile['setting'].as_bool('use_zarafa_credentials'),
"use_zarafa_credentials": configfile['setting']['use_zarafa_credentials'],
"user": encode(username).decode('utf-8'),
"password": password.decode('utf-8'),
"server_port": encode(port).decode('utf-8')

View File

@ -1,13 +0,0 @@
[setting]
name = FTP server
type = FTP
workgroup =
server_path = /srv/ftp
server_address = 10.10.11.9
server_ssl = false
use_zarafa_credentials = false
server_port = 21
default_user = ftpuser
default_password = ftpuser
server_pasv = false

View File

@ -4,7 +4,7 @@ name = Samba share
type = SMB
workgroup = zarafa
server_path = files
server_address = 10.10.11.9
server_address = 192.168.1.230
server_ssl = false
use_zarafa_credentials = true
server_port = 80

View File

@ -1,12 +0,0 @@
Copyright (C) 2019 Kopano and its licensors
This program is free software: you can redistribute it and/or modify it under the terms of the
GNU Affero General Public License as published by the Free Software Foundation, either version
3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along with this program.
If not, see http://www.gnu.org/licenses/.

View File

@ -2,10 +2,8 @@
#encoding: utf-8
import kopano
from kopano.errors import NotFoundError
from MAPI.Util import *
import json
import sys
def opt_args():
@ -26,22 +24,14 @@ def main():
options, args = opt_args()
if not options.user:
print('Please use:\n %s --user <username>' % (sys.argv[0]))
print 'Please use:\n %s --user <username>' % (sys.argv[0])
sys.exit(0)
user = kopano.Server(options).user(options.user)
try:
webapp = user.store.prop(0X6773001F).value
except NotFoundError:
webapp = dict(recipients=[])
webapp = user.store.prop(0X6773001F).value
webapp = json.loads(webapp)
if options.backup:
if len(webapp['recipients']) == 0:
print('Property PR_EC_RECIPIENT_HISTORY_JSON_W not found . User might have never used recipient history before.', file=sys.stderr)
sys.exit(1)
f = open('%s.json' % user.name, 'w')
f.write(json.dumps(webapp, sort_keys=True,
indent=4, separators=(',', ': ')))
@ -60,8 +50,8 @@ def main():
sys.exit(0)
if options.list:
print(json.dumps(webapp, sort_keys=True,
indent=4, separators=(',', ': ')))
print json.dumps(webapp, sort_keys=True,
indent=4, separators=(',', ': '))
sys.exit(0)
if options.remove:
@ -69,7 +59,7 @@ def main():
for rec in webapp['recipients']:
if options.remove in rec['display_name'] or options.remove in rec['smtp_address'] \
or options.remove in rec['email_address']:
print('removing contact %s [%s]' % (rec['display_name'], rec['smtp_address']))
print 'removing contact %s [%s]' % (rec['display_name'], rec['smtp_address'])
else:
newlist['recipients'].append(rec)

View File

@ -31,8 +31,4 @@ Remove all recipients that have example.com in there display_name, smtp_address
python remove_recipients.py --user user --remove example.com
```
# License
licensed under GNU Affero General Public License v3.

View File

@ -1,12 +0,0 @@
Copyright (C) 2019 Kopano and its licensors
This program is free software: you can redistribute it and/or modify it under the terms of the
GNU Affero General Public License as published by the Free Software Foundation, either version
3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along with this program.
If not, see http://www.gnu.org/licenses/.

View File

@ -1,14 +1,11 @@
# WebApp Admin
>**Always make a backup of the user settings and test the new settings afterwards**
>**This tool is under contruction. Use caution on a live server. Always make a backup of the user settings and test first before modifing**
WebApp admin is a command-line interface to modify, inject and export WebApp settings.
# Example Usage
Overview of all options:
> python3 webapp_admin -h
Reset WebApp settings
> python3 webapp_admin -u john --reset
@ -18,35 +15,6 @@ Change free/busy to 36 months
If you want to make a change for all users pass the --all-users parameter. Example:
> python3 webapp_admin --all-users --icons Breeze
## Signatures
To restore, replace and backup signatures we need a two part, underscore separated filename consisting of a `name` and `id`.\
Example single user: `this-is-my-signature_1234.html`\
---
**Note**\
The hypens in the filename will be displayed as spaces in WebApp\
The username can also be part of the .html file, but is then ignored by the script.
In WebApp the ID is created based on the unix time, so the ID can be anything
---
Examples
Backup signature for user `henk`
> python3 webapp_admin -u henk --backup-signature
Restore signature for user `henk`
> python3 webapp_admin -u henk --restore-signature my-cool-signature_1615141312112.html
Replace signature for user `henk`
> python3 webapp_admin -u henk --replace-signature my-cool-signature_1615141312112.html
Restore signatures for all users
> python3 webapp_admin --all-users --restore-signature mycompany-signature_1412130992124.html
# Dependencies
- python3
@ -54,9 +22,3 @@ Restore signatures for all users
- python-mapi
- OpenSSL
- dotty_dict
For debian 10 python3-pkg-resources is required
# License
licensed under GNU Affero General Public License v3.

View File

@ -1,6 +1,5 @@
#!/usr/bin/env python3
# encoding: utf-8
from pkg_resources import parse_version
import sys
try:
import kopano
@ -17,17 +16,17 @@ import base64
try:
import OpenSSL.crypto
except ImportError:
pass
from datetime import datetime, timedelta
print('pip3 install pyOpenSSL')
sys.exit(1)
from datetime import datetime
from time import mktime
import getpass
import time
from optparse import OptionGroup
from tabulate import tabulate
try:
from dotty_dict import dotty
except ImportError:
pass
print('dotty_dict not found on your system. Run pip3 install dotty_dict')
"""
@ -51,20 +50,11 @@ def opt_args(print_help=None):
group.add_option("--reset", dest="reset", action="store_true", help="Reset WebApp settings")
parser.add_option_group(group)
# Addionals stores group
group = OptionGroup(parser, "Store", "")
group.add_option("--add-store", dest="add_store", action="store", help="Add shared store")
group.add_option("--del-store", dest="del_store", action="store", help="Delete shared store")
group.add_option("--folder-type", dest="folder_type", action="store", help="Folder to add")
group.add_option("--subfolder", dest="sub_folder", action="store_true", help="Add subfolders")
group.add_option("--list-stores", dest="list_stores", action="store_true", help="List shared stores")
parser.add_option_group(group)
# Signature option group
group = OptionGroup(parser, "Signature", "")
group.add_option("--backup-signature", dest="backup_signature", action="store_true", help="Backup signature")
group.add_option("--restore-signature", dest="restore_signature", action="store", help="Restore signature (need file name)")
group.add_option("--replace-signature", dest="replace_signature", action="store", help="Replace existing signature, file layout must be: username_signature-name_signatureid.html or signature-name_signatureid.html ")
group.add_option("--replace", dest="replace", action="store_true", help="Replace existing signature, file layout must be: username_signature-name_signatureid.html")
group.add_option("--default-signature", dest="default_signature", action="store_true", help="Set signature as default one")
parser.add_option_group(group)
@ -72,13 +62,12 @@ def opt_args(print_help=None):
group = OptionGroup(parser, "Categories", "")
group.add_option("--export-categories", dest="export_categories", action="store_true", help="Export Categories (name and color)")
group.add_option("--import-categories", dest="import_categories", action="store_true", help="Import Categories (name and color)")
parser.add_option_group(group)
parser.add_option_group(group)
# S/MIME option group
group = OptionGroup(parser, "S/MIME", "")
group.add_option("--export-smime", dest="export_smime", action="store_true", help="Export private S/MIME certificate")
group.add_option("--import-smime", dest="import_smime", action="store", help="Import private S/MIME certificate")
group.add_option("--remove-expired", dest="remove_expired", action="store_true", help="Remove expired public S/MIME certificates")
group.add_option("--public", dest="public_smime", action="store_true", help="Export/Import public S/MIME certificate")
group.add_option("--password", dest="password", action="store", help="set password")
group.add_option("--ask-password", dest="ask_password", action="store_true", help="ask for password if needed")
@ -86,15 +75,11 @@ def opt_args(print_help=None):
# WebApp setting option group
group = OptionGroup(parser, "webapp-settings", "")
group.add_option("--language", dest="language", action="store", type="string", help="Set new language (e.g. en_GB or nl_NL)")
group.add_option("--language", dest="language", action="store", help="Set new language (e.g. en_GB or nl_NL)")
group.add_option("--theme", dest="theme", action="store", help="Change theme (e.g. dark)")
group.add_option("--free-busy", dest="freebusy", action="store", help="Change free/busy time span in months")
group.add_option("--icons", dest="icons", action="store", help="Change icons (e.g. breeze)")
group.add_option("--htmleditor", dest="htmleditor", action="store", help="Change the HTML editor (e.g. full_tinymce)")
group.add_option("--remove-state", dest="remove_state", action="store_true", help="Remove all the state settings")
group.add_option("--add-safesender", dest="add_sender", action="store", help="Add domain to safe sender list")
group.add_option("--polling-interval", dest="polling_interval", action="store", help="Change the polling interval (seconds)")
group.add_option("--calendar-resolution", dest="calendar_resolution", action="store", help="Change the calendar resolution (minutes)")
parser.add_option_group(group)
# Advanced option group
@ -209,73 +194,6 @@ def language(user, language):
write_settings(user, json.dumps(settings))
"""
Add shared store
"""
def add_store(user, user_to_add, folder_type, subfolder=False):
allowed_folder_types = ["all", "inbox", "calendar", "contact", "note", "task"]
if folder_type not in allowed_folder_types:
print("Unknown folder type allowed: {}".format(','.join(allowed_folder_types)))
sys.exit(1)
settings = read_settings(user)
if not settings['settings']['zarafa']['v1']['contexts'].get('hierarchy') or not settings['settings']['zarafa']['v1']['contexts']['hierarchy'].get('shared_stores'):
settings['settings']['zarafa']['v1']['contexts']['hierarchy']['shared_stores'] = {}
settings['settings']['zarafa']['v1']['contexts']['hierarchy']['shared_stores'][user_to_add]= {folder_type : {'folder_type': folder_type, 'show_subfolders': subfolder}}
print("Saving settings")
write_settings(user, json.dumps(settings))
"""
Delete shared store
"""
def del_store(user, user_to_del, folder_type=None):
if folder_type:
allowed_folder_types = ["all", "inbox", "calendar", "contact", "note", "task"]
if folder_type not in allowed_folder_types:
print("Unknown folder type allowed: {}".format(','.join(allowed_folder_types)))
sys.exit(1)
settings = read_settings(user)
if not settings['settings']['zarafa']['v1']['contexts'].get('hierarchy') or not settings['settings']['zarafa']['v1']['contexts']['hierarchy'].get('shared_stores'):
print("No additional stores found")
return
shared_store = settings['settings']['zarafa']['v1']['contexts']['hierarchy']['shared_stores'].get(user_to_del)
if not shared_store:
print("No additional stores found")
return
try:
if not folder_type:
settings['settings']['zarafa']['v1']['contexts']['hierarchy']['shared_stores'].pop(user_to_del)
else:
settings['settings']['zarafa']['v1']['contexts']['hierarchy']['shared_stores'][user_to_del].pop(folder_type)
except KeyError:
pass
print("Saving settings")
write_settings(user, json.dumps(settings))
"""
List all added stores
"""
def list_stores(user):
settings = read_settings(user)
try:
stores = settings['settings']['zarafa']['v1']['contexts']['hierarchy']['shared_stores']
except KeyError:
print("No additional stores found")
return
table_header = ["User", 'Folder type', 'Show subfolders']
table_data =[]
for user in stores:
for folder in stores[user]:
table_data.append([user, folder, stores[user][folder]['show_subfolders']])
print(tabulate(table_data, headers=table_header,tablefmt="grid"))
"""
Backup signature from the users store
@ -292,8 +210,6 @@ def backup_signature(user, location=None):
except Exception as e:
print('Could not load WebApp settings for user {} (Error: {})'.format(user.name, repr(e)))
sys.exit(1)
if not settings['settings']['zarafa']['v1']['contexts'].get("mail"):
settings['settings']['zarafa']['v1']['contexts']['mail'] = {}
if settings['settings']['zarafa']['v1']['contexts']['mail'].get('signatures'):
for item in settings['settings']['zarafa']['v1']['contexts']['mail']['signatures']['all']:
name = settings['settings']['zarafa']['v1']['contexts']['mail']['signatures']['all'][item]['name']
@ -317,31 +233,16 @@ Restore signature into the users store
def restore_signature(user, filename, replace=None, default=None):
restorefile = filename
filename_split = filename.split('_')
if len(filename_split) == 2:
signaturename = filename_split[0].replace('-',' ')
if replace:
signatureid = filename_split[1].split('.')[0]
elif len(filename_split) == 3:
signaturename = filename_split[1].replace('-',' ')
if replace:
signatureid = filename_split[2].split('.')[0]
else:
if replace:
print('File format is not supported')
sys.exit(1)
with open(restorefile, 'r') as sigfile:
signaturehtml = sigfile.read()
if replace:
signatureid = filename.split('_')[2].split('.')[0]
action = 'Replacing'
else:
signaturename = filename.split('.')[0]
signatureid = int(time.time())
action = 'Adding'
signaturename = filename.split('_')[1].replace('-',' ')
signaturecontent = dict(
{u'name': signaturename, u'content': signaturehtml, u'isHTML': True})
settings = read_settings(user)
@ -368,7 +269,6 @@ def restore_signature(user, filename, replace=None, default=None):
write_settings(user, json.dumps(settings))
"""
Export categories from users store
@ -415,11 +315,11 @@ def import_categories(user, filename=None):
if not user.store.get_prop(PR_EC_WEBAPP_PERSISTENT_SETTINGS_JSON_W):
persistent_settings ={'settings': {'kopano': {'main': {'categories':data}}}}
else:
persistent_settings = json.loads(user.store.get_prop(PR_EC_WEBAPP_PERSISTENT_SETTINGS_JSON_W).value)
persistent_settings = user.store.get_prop(PR_EC_WEBAPP_PERSISTENT_SETTINGS_JSON_W)
persistent_settings['settings']['kopano']['main']['categories'] = data
print('Restoring categories for user {}'.format(user.name))
user.store.create_prop(PR_EC_WEBAPP_PERSISTENT_SETTINGS_JSON_W, json.dumps(persistent_settings))
user.store.create_prop(PR_EC_WEBAPP_PERSISTENT_SETTINGS_JSON_W, json.dumps(persistent_settings).decode('utf-8'))
"""
@ -430,7 +330,6 @@ Export S/MIME certificate from users store
:param public: Export public certificate part
"""
def export_smime(user, location=None, public=None):
if location:
backup_location = location
else:
@ -443,7 +342,7 @@ def export_smime(user, location=None, public=None):
return
for cert in certificates:
if public and cert.prop(PR_MESSAGE_CLASS_W).value == 'WebApp.Security.Public':
if public and cert.prop(PR_MESSAGE_CLASS_w).value == 'WebApp.Security.Public':
extension = 'pub'
body = cert.text
else:
@ -465,9 +364,6 @@ Import S/MIME certificate into users store
:param public: Import public certificate part
"""
def import_smime(user, cert_file, passwd, ask_password=None, public=None):
if not sys.modules.get('OpenSSL'):
print('PyOpenSSl not installed \npip3 install pyOpenSSL')
sys.exit(1)
if ask_password:
passwd = getpass.getpass()
elif not passwd:
@ -523,27 +419,6 @@ def import_smime(user, cert_file, passwd, ask_password=None, public=None):
else:
print('Email address doesn\'t match')
"""
Remove expired S/MIME Public certificates
:param user: The user
"""
def remove_expired_smime(user):
# unable to loop over the associated items so getting the items in a list instead
certificates =list(user.store.root.associated.items())
if len(certificates) == 0:
print('No certificates found')
return
now = datetime.now()
for cert in certificates:
# We only want to remove the public certificate
if cert.prop(PR_MESSAGE_CLASS_W).value == 'WebApp.Security.Public':
if cert.prop(PR_MESSAGE_DELIVERY_TIME).value < now:
print('deleting public certificate {} ({})'.format(cert.subject, cert.prop(PR_MESSAGE_DELIVERY_TIME).value))
user.store.root.associated.delete(cert)
"""
Custom function to merge two dictionaries.
@ -553,7 +428,6 @@ but this function caused undesired behavior
:param dict1: The first dictionary
:param dict2: The second dictionary
"""
def mergedicts(dict1, dict2):
for k in set(dict1.keys()).union(dict2.keys()):
if k in dict1 and k in dict2:
@ -573,21 +447,11 @@ Inject webapp settings into the users store
:param user: The user
:param data: The webapp setting
"""
def advanced_inject(user, data, value_type='string'):
if not sys.modules.get('dotty_dict'):
print('dotty_dict not found on your system. \nRun pip3 install dotty_dict')
sys.exit(1)
def advanced_inject(user, data):
settings = read_settings(user)
split_data = data.split('=')
value = split_data[1].lstrip().rstrip()
if value.lower() == 'true':
value = True
elif value.lower() == 'false':
value = False
if value_type == 'list':
value = value.split(',')
dot = dotty()
dot[split_data[0].rstrip()] = value
@ -605,15 +469,14 @@ def main():
opt_args(True)
options, args = opt_args()
# Always first!
# If the script should execute for all users
# The admin should pass the '--all-users' parameter
if not options.users and not options.all_users:
print('There are no users specified. Use "--all-users" to run for all users')
sys.exit(1)
server = kopano.Server(options)
for user in server.users(options.users):
for user in kopano.Server(options).users(options.users):
# Backup and restore
if options.backup:
backup(user, options.location)
@ -624,36 +487,22 @@ def main():
if options.language:
language(user, options.language)
if options.add_store:
add_store(user, options.add_store, options.folder_type, options.sub_folder)
if options.del_store:
del_store(user, options.del_store, options.folder_type)
if options.list_stores:
list_stores(user)
#Categories
if options.export_categories:
export_categories(user, options.file)
export_categories(user, options.change_locale)
if options.import_categories:
import_categories(user, options.file)
# S/MIME import/export
if options.export_smime:
export_smime(user, options.location, options.public_smime)
if options.import_smime:
import_smime(user, options.import_smime, options.password, options.ask_password, options.public_smime)
if options.remove_expired:
remove_expired_smime(user)
# Signature
if options.backup_signature:
backup_signature(user, options.location)
if options.restore_signature:
restore_signature(user, options.restore_signature, False, options.default_signature)
if options.replace_signature:
restore_signature(user, options.replace_signature, True, options.default_signature)
restore_signature(user, options.restore_signature, options.replace, options.default_signature)
# Advanced injection option
if options.add_option:
@ -685,7 +534,7 @@ def main():
sys.exit(1)
setting = 'settings.zarafa.v1.main.active_iconset = {}'.format(options.icons)
advanced_inject(user, setting)
print('Icon set changed to {}'.format(options.icons))
print('icon set changed to {}'.format(options.icons))
# Editor
if options.htmleditor:
@ -697,47 +546,6 @@ def main():
advanced_inject(user, setting)
print('Editor changed to {}'.format(options.htmleditor))
# State settings
if options.remove_state:
settings = read_settings(user)
settings['settings']['zarafa']['v1']['state'] = {}
write_settings(user, json.dumps(settings))
print('Removed state settings for {}'.format(user.name))
# Add sender to safe sender list
if options.add_sender:
settings = read_settings(user)
setting = 'settings.zarafa.v1.contexts.mail.safe_senders_list = {}'.format(options.add_sender)
advanced_inject(user, setting, 'list')
print('{}'.format(options.add_sender), 'Added to safe sender list for {}'.format(user.name))
# Polling interval
if options.polling_interval:
try:
value = int(options.polling_interval)
except ValueError:
print('Invalid number used. Please specify the value in seconds')
sys.exit(1)
settings = read_settings(user)
setting = 'settings.zarafa.v1.main.reminder.polling_interval = {}'.format(options.polling_interval)
advanced_inject(user, setting)
print('Polling interval changed to', '{}'.format(options.polling_interval), 'for {}'.format(user.name))
# Calendar resolution (zoom level)
if options.calendar_resolution:
try:
value = int(options.calendar_resolution)
except ValueError:
print('Invalid number used. Please specify the value in minutes')
sys.exit(1)
if value < 5 or value > 60:
print('Unsupported value used. Use a number between 5 and 60')
sys.exit(1)
settings = read_settings(user)
setting = 'settings.zarafa.v1.contexts.calendar.default_zoom_level = {}'.format(options.calendar_resolution)
advanced_inject(user, setting)
print('Calendar resolution changed to', '{}'.format(options.calendar_resolution), 'for {}'.format(user.name))
# Always at last!!!
if options.reset:
reset_settings(user)