/usr/share/pyshared/desktopcouch/application/local_files.py is in python-desktopcouch-application 1.0.8-0ubuntu3.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 | # Copyright 2009 Canonical Ltd.
#
# This file is part of desktopcouch.
#
# desktopcouch is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# desktopcouch 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with desktopcouch. If not, see <http://www.gnu.org/licenses/>.
#
# Author: Stuart Langridge <stuart.langridge@canonical.com>
# Eric Casteleijn <eric.casteleijn@canonical.com>
"""Specify location of files relevant to desktop CouchDB.
Checks to see whether we're running out of the source tree or not.
"""
from __future__ import with_statement
import os
import errno
import logging
import tempfile
import re
from desktopcouch.application.platform import (Keyring, CACHE_HOME,
save_data_path, save_config_path)
# pylint: disable=F0401
try:
import ConfigParser as configparser
except ImportError:
import configparser
# pylint: enable=F0401
COUCH_EXE = os.environ.get('COUCHDB')
if not COUCH_EXE:
for segment in os.environ['PATH'].split(':'):
if os.path.exists(os.path.join(segment, 'couchdb')):
COUCH_EXE = os.path.join(segment, 'couchdb')
if not COUCH_EXE:
raise ImportError("Could not find couchdb")
def _write_bookmark(bookmark_file, admin_username, admin_password):
"""Write the bookmark used to access couchdb."""
fp = open(bookmark_file, "w")
try:
fp.write("Desktopcouch is not yet started.\n")
fp.write("<!-- Couch clobbers useful creds in INI file. -->\n")
fp.write("<!-- So, store creds in the place we need it. -->\n")
fp.write("<!-- The '!' fmt is perpetual but port varies. -->\n")
fp.write(
"<!-- !!%(admin_username)s!!%(admin_password)s!! -->\n" % {
'admin_username': admin_username,
'admin_password': admin_password})
fp.write("<!-- ^^ Seeded only creds into bookmark file. -->\n")
fp.write("<!-- Will flesh out with port when we know it. -->\n")
fp.write("<!-- Replacement text also uses creds format. -->\n")
finally:
fp.close()
class _Configuration(object):
"""Configuration object."""
def __init__(self, ctx):
super(_Configuration, self).__init__()
self.file_name_used = ctx.file_ini
self._c = configparser.SafeConfigParser()
# monkeypatch ConfigParser to stop it lower-casing option names
self._c.optionxform = lambda s: s
bookmark_file = os.path.join(ctx.db_dir, "couchdb.html")
try:
bookmark_file_contents = open(bookmark_file).read()
if "-hashed-" in bookmark_file_contents:
raise ValueError("Basic-auth cred lost.")
# trial run, check sanity.
# pylint: disable=W0106
re.findall(
"<!-- !!([^!]+)!!([^!]+)!! -->",
bookmark_file_contents)[-1]
# pylint: enable=W0106
self._fill_from_file(self.file_name_used)
return
except (IOError, ValueError, IndexError):
pass # Loading failed. Let's fill it with sensible defaults.
local = {
'couchdb': {
'database_dir': ctx.db_dir,
'view_index_dir': ctx.db_dir,
},
'httpd': {
'bind_address': '127.0.0.1',
'port': '0',
'WWW-Authenticate': 'Basic realm="bookmarkable-user-auth"'
},
'log': {
'file': ctx.file_log,
'level': ctx.couchdb_log_level,
},
'stats': { # disable statistics gathering
'rate': '',
'samples': '',
},
'daemons': { # disable statistics gathering
'stats_collector': '',
'stats_aggregator': '',
},
'httpd_global_handlers': {
'_stats': '', # disable statisics URL
},
}
if ctx.with_auth:
admin_username, admin_password = \
ctx.keyring.get_user_name_password()
consumer_key, consumer_secret, token, token_secret = \
ctx.keyring.get_oauth_data()
local.update({'admins': {
admin_username: admin_password},
'oauth_consumer_secrets': {
consumer_key: consumer_secret},
'oauth_token_secrets': {
token: token_secret},
'oauth_token_users': {
token: admin_username},
'couch_httpd_auth': {
'require_valid_user': 'true'}})
_write_bookmark(bookmark_file, admin_username,
admin_password)
self._fill_from_structure(local)
self.save_to_file(self.file_name_used)
def _fill_from_structure(self, structure):
"""Fill from structure."""
for section_name in structure:
for key in structure[section_name]:
self.set_item(section_name, key, structure[section_name][key])
def _fill_from_file(self, file_name):
"""Fill from file."""
with file(file_name) as f:
self._c.readfp(f)
def save_to_file(self, file_name):
"""Save to file."""
container = os.path.split(file_name)[0]
fd, temp_file_name = tempfile.mkstemp(dir=container)
f = os.fdopen(fd, "w")
try:
self._c.write(f)
finally:
f.close()
os.rename(temp_file_name, file_name)
def items_in_section(self, section_name):
"""Return the items in the section."""
try:
return self._c.items(section_name)
except configparser.NoSectionError:
raise ValueError("Section %r not present." % (section_name,))
def set_item(self, section_name, key, value):
"""Set an item."""
if not self._c.has_section(section_name):
self._c.add_section(section_name)
self._c.set(section_name, key, value)
def sync(self):
"""Write back to the file named when we tried to read in init."""
self.save_to_file(self.file_name_used)
def __str__(self):
return self.file_name_used
class Context():
"""A mimic of xdg BaseDirectory, with overridable values that do not
depend on environment variables."""
def __init__(self, run_dir, db_dir, config_dir, with_auth=True,
keyring=None): # (cache, data, config)
self.couchdb_log_level = 'notice'
for d in (run_dir, db_dir, config_dir):
try:
os.makedirs(d, 0700)
except OSError:
pass # Probably that it already exists.
try:
os.chmod(d, 0700)
except OSError:
logging.warn("Failed to chmod %s", d)
self.run_dir = os.path.realpath(run_dir)
self.config_dir = os.path.realpath(config_dir)
self.db_dir = os.path.realpath(db_dir)
self.with_auth = with_auth
self.keyring = keyring
self.file_ini = os.path.join(config_dir, "desktop-couchdb.ini")
self.file_pid = os.path.join(run_dir, "desktop-couchdb.pid")
self.file_log = os.path.join(run_dir, "desktop-couchdb.log")
self.file_stdout = os.path.join(run_dir, "desktop-couchdb.stdout")
self.file_stderr = os.path.join(run_dir, "desktop-couchdb.stderr")
# You will need to add -b or -k on the end of this
self.couch_exec_command = [COUCH_EXE, self.couch_chain_ini_files(),
'-p', self.file_pid,
'-o', self.file_stdout,
'-e', self.file_stderr]
self.configuration = _Configuration(self)
def sanitize_log_files(self):
"""Sanitize the log files."""
for descr in ("ini", "pid", "log", "stdout", "stderr",):
f = getattr(self, "file_" + descr)
if os.path.isfile(f):
os.chmod(f, 0600)
for descr in ("log", "stdout", "stderr",):
f = getattr(self, "file_" + descr)
for src_ext, dst_ext in ("23", "12", (None, "1")):
if src_ext is None:
srcname = f
else:
srcname = "%s.%s" % (f, src_ext)
dstname = "%s.%s" % (f, dst_ext)
try:
os.rename(srcname, dstname)
except OSError, e:
if e.errno != errno.ENOENT:
logging.exception("failed to back-up %s", srcname)
def load_config_paths(self):
"""This is xdg/BaseDirectory.py load_config_paths() with hard-code to
use desktop-couch resource and read from this context."""
yield self.config_dir
for config_dir in \
os.environ.get('XDG_CONFIG_DIRS', '/etc/xdg').split(':'):
path = os.path.join(config_dir, "desktop-couch")
if os.path.exists(path):
yield path
def couch_chain_ini_files(self):
"""Chain couchdb ini files."""
# Explicitly add default ini file
ini_files = [os.environ.get("COUCHDB_INI") or
"/etc/couchdb/default.ini"]
# find all ini files in the desktopcouch XDG_CONFIG_DIRS and
# add them to the chain
config_dirs = self.load_config_paths()
# Reverse the list because it's in most-important-first order
for folder in reversed(list(config_dirs)):
ini_files.extend([os.path.join(folder, x)
for x in sorted(os.listdir(folder))
if x.endswith(".ini")])
if self.file_ini not in ini_files:
ini_files.append(self.file_ini)
chain = "-n -a %s " % " -a ".join(ini_files)
return chain
class NoOAuthTokenException(Exception):
"""Exception when OAuth token is missing."""
def __init__(self, file_name):
super(NoOAuthTokenException, self).__init__()
self.file_name = file_name
def __str__(self):
return "OAuth details were not found in the ini file (%s)" % (
self.file_name)
DEFAULT_CONTEXT = Context(
os.path.join(CACHE_HOME, "desktop-couch"),
save_data_path("desktop-couch"),
save_config_path("desktop-couch"),
keyring=Keyring())
def get_admin_credentials(ctx=DEFAULT_CONTEXT):
"""Get administrator credentials for CouchDB"""
return ctx.configuration.items_in_section("admins")[0]
def get_oauth_tokens(ctx=DEFAULT_CONTEXT):
"""Return the OAuth tokens from the desktop Couch ini file.
CouchDB OAuth is two-legged OAuth (not three-legged like most
OAuth). We have one"consumer", defined by a consumer_key and a secret,
and an "access token", defined by a token and a secret.
The OAuth signature is created with reference to these and the requested
URL.
(More traditional 3-legged OAuth starts with a"request token" which is
then used to procure an "access token". We do not require this.)
"""
cf = ctx.configuration
try:
oauth_token_secrets = cf.items_in_section("oauth_token_secrets")[0]
oauth_consumer_secrets = cf.items_in_section(
"oauth_consumer_secrets")[0]
except configparser.NoSectionError:
raise NoOAuthTokenException(cf)
except IndexError:
raise NoOAuthTokenException(cf)
if not oauth_token_secrets or not oauth_consumer_secrets:
raise NoOAuthTokenException(cf)
try:
out = {
"token": oauth_token_secrets[0],
"token_secret": oauth_token_secrets[1],
"consumer_key": oauth_consumer_secrets[0],
"consumer_secret": oauth_consumer_secrets[1]}
except IndexError:
raise NoOAuthTokenException(cf)
return out
def get_bind_address(ctx=DEFAULT_CONTEXT):
"""Retrieve a string if it exists, or None if it doesn't."""
for key, value in ctx.configuration.items_in_section("httpd"):
if key == "bind_address":
return value
def set_bind_address(address, ctx=DEFAULT_CONTEXT):
"""Set the bind address."""
ctx.configuration.set_item("httpd", "bind_address", address)
|