cinder/cinder/volume/drivers/datacore/passwd.py

172 lines
5.4 KiB
Python

# Copyright (c) 2017 DataCore Software Corp. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Password storage."""
import json
import os
import stat
from oslo_log import log as logging
from cinder.i18n import _
from cinder import utils as cinder_utils
LOG = logging.getLogger(__name__)
class FileStorage(object):
"""Represents a file as a dictionary."""
def __init__(self, file_path):
self._file_path = file_path
self._file = None
self._is_open = False
def open(self):
"""Open a file for simultaneous reading and writing.
If the specified file does not exist, it will be created
with the 0600 access permissions for the current user, if needed
the appropriate directories will be created with the 0750 access
permissions for the current user.
"""
file_dir = os.path.dirname(self._file_path)
if file_dir and not os.path.isdir(file_dir):
os.makedirs(file_dir)
os.chmod(file_dir, stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP)
if not os.path.isfile(self._file_path):
open(self._file_path, 'w').close()
os.chmod(self._file_path, stat.S_IRUSR | stat.S_IWUSR)
if self._file:
self.close()
self._file = open(self._file_path, 'r+')
return self
def load(self):
"""Reads the file and returns corresponded dictionary object.
:return: The dictionary that represents the file content.
"""
storage = {}
if os.stat(self._file_path).st_size != 0:
storage = json.load(self._file)
if not isinstance(storage, dict):
msg = _('File %s has a malformed format.') % self._file_path
raise ValueError(msg)
return storage
def save(self, storage):
"""Writes the specified dictionary to the file.
:param storage: Dictionary that should be written to the file.
"""
if not isinstance(storage, dict):
msg = _('%s is not a dict.') % repr(storage)
raise TypeError(msg)
self._file.seek(0)
self._file.truncate()
json.dump(storage, self._file)
def close(self):
"""Close the file."""
if self._file:
self._file.close()
self._file = None
def __enter__(self):
return self
def __exit__(self, type, value, traceback):
self.close()
class PasswordFileStorage(object):
"""Password storage implementation.
It stores passwords in a file in a clear text. The password file must be
secured by setting up file permissions.
"""
def __init__(self, file_path):
self._file_path = file_path
self._file_storage = FileStorage(file_path)
def set_password(self, resource, username, password):
"""Store the credential for the resource.
:param resource: Resource name for which credential will be stored
:param username: User name
:param password: Password
"""
@cinder_utils.synchronized(
'datacore-password_storage-' + self._file_path, external=True)
def _set_password():
with self._file_storage.open() as storage:
passwords = storage.load()
if resource not in passwords:
passwords[resource] = {}
passwords[resource][username] = password
storage.save(passwords)
_set_password()
def get_password(self, resource, username):
"""Returns the stored password for the resource.
If the password does not exist, it will return None
:param resource: Resource name for which credential was stored
:param username: User name
:return password: Password
"""
@cinder_utils.synchronized(
'datacore-password_storage-' + self._file_path, external=True)
def _get_password():
with self._file_storage.open() as storage:
passwords = storage.load()
if resource in passwords:
return passwords[resource].get(username)
return _get_password()
def delete_password(self, resource, username):
"""Delete the stored credential for the resource.
:param resource: Resource name for which credential was stored
:param username: User name
"""
@cinder_utils.synchronized(
'datacore-password_storage-' + self._file_path, external=True)
def _delete_password():
with self._file_storage.open() as storage:
passwords = storage.load()
if resource in passwords and username in passwords[resource]:
del passwords[resource][username]
if not passwords[resource].keys():
del passwords[resource]
storage.save(passwords)
_delete_password()