# -*- coding: utf-8 -*-
#
# secrets.py
# ----------
#
# Copyright (C) 2007 Jeremie Dimino <dimino@crans.org>
# Copyright (C) 2014 Daniel STAN <daniel.stan@crans.org>
#
# This file is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This file 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.

"""
Recuperation des secrets depuis /etc/crans/secrets.
"""

import sys
import os
import logging
import logging.handlers
import getpass
import functools

SECRET_PATH = '/etc/crans/secrets'

# Initialisation d'un logger pour faire des stats etc
# pour l'instant, on centralise tout sur thot en mode debug
logger = logging.getLogger('secrets_new')
logger.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(name)s: [%(levelname)s] %(message)s')
handler = logging.handlers.SysLogHandler(address = '/dev/log')
try:
    handler.addFormatter(formatter)
except AttributeError:
    handler.formatter = formatter
logger.addHandler(handler)

class SecretNotFound(Exception):
    """Le secret n'a pas été trouvé"""
    pass

class SecretForbidden(Exception):
    """Un secret a été trouvé mais nous n'avons pas le droit de le lire.
    Lancer cette exception signifie que tout chargement ultérieur du secret
    par d'autres moyens est abandonné"""
    pass

# Définitions de fonctions renvoyant un secret, si existant, en utilisant
# **UNE** méthode d'accès
def python_loader(name):
    """Charger depuis le fichier python la variable au ``name`` correspondant"""
    try:
        sys.path.insert(0, SECRET_PATH)
        import secrets as module
        sys.path.pop(0)
        try:
            return getattr(module, name)
        except AttributeError:
            raise SecretNotFound()
    except ImportError:
        raise SecretForbidden()

def single_file_loader(name, secret_path=SECRET_PATH, fatal_io=True):
    """Charger depuis un fichier isolé appelé ``name``,
    Si ``fatal_io`` est à False, une erreur d'accès au fichier (droits)
    sera considérée comme un fichier absent, et le mécanisme pourra
    ainsi continuer avec d'autres loaders.
    """
    path = os.path.join(secret_path, name)
    if not os.path.isfile(path):
        raise SecretNotFound()
    try:
        with open(path, 'r') as source:
            result = source.read().strip()
        return result
    except IOError:
        if fatal_io:
            raise SecretForbidden()
        else:
            raise SecretNotFound()

def get(name):
    """ Récupère un secret. """
    prog = os.path.basename(getattr(sys, 'argv', ['undefined'])[0])
    logger.debug('%s (in %s) asked for %s' % (getpass.getuser(), prog, name))

    loaders = [python_loader, single_file_loader]
    if os.getenv('DBG_SECRETS'):
        dbg_loader = functools.partial(single_file_loader,
            secret_path=os.getenv('DBG_SECRETS'),
            fatal_io=False)
        loaders.insert(0, dbg_loader)
    notfound_error = None

    for loader in loaders:
        try:
            return loader(name)
        except SecretNotFound as exc:
            notfound_error = notfound_error or exc
        except SecretForbidden:
            if not os.getenv('DBG_SECRETS'):
                logger.critical('...and that failed (Forbidden).')
                raise

    logger.critical('...and that failed (not found).')
    raise notfound_error
