plasma-framework/private/serviceprovider.cpp

438 lines
17 KiB
C++
Raw Normal View History

/*
* Copyright © 2009 Rob Scheepmaker <r.scheepmaker@student.utwente.nl>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License version 2 as
* published by the Free Software Foundation
*
* 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 General Public License for more details
*
* You should have received a copy of the GNU Library General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "serviceprovider_p.h"
#include "authorizationrule_p.h"
#include "authorizationmanager_p.h"
#include "joliemessagehelper_p.h"
#include "config-plasma.h"
#include <plasma/remote/authorizationinterface.h>
#include <plasma/remote/authorizationmanager.h>
#include <plasma/remote/authorizationrule.h>
#include <plasma/remote/credentials.h>
#include <plasma/service.h>
#include <plasma/servicejob.h>
#include <plasma/private/servicejob_p.h>
#include <QtCore/QBuffer>
#include <QtCore/QFile>
#include <QtJolie/Server>
#ifdef ENABLE_REMOTE_WIDGETS
#include <QtCrypto>
#endif
#include <kdebug.h>
#include <kstandarddirs.h>
namespace Plasma
{
ServiceProvider::ServiceProvider(const QString &name, Service *service)
: Jolie::AbstractAdaptor(service),
m_service(service)
{
connect(service, SIGNAL(finished(Plasma::ServiceJob *)),
this, SLOT(operationCompleted(Plasma::ServiceJob*)));
m_providerName = name;
AuthorizationManager::self()->d->server->registerAdaptor(m_providerName.toUtf8(), this);
kDebug() << "registered service provider " << m_providerName;
}
ServiceProvider::~ServiceProvider()
{
AuthorizationManager::self()->d->server->unregisterAdaptor(m_providerName.toUtf8());
}
void ServiceProvider::startOperationCall(Jolie::Message message)
{
kDebug() << "starting operation call";
KConfigGroup description =
m_service->operationDescription(QString(Message::field(Message::Field::OPERATION, message)));
//deserialize the parameters
QByteArray parametersByteArray;
parametersByteArray = Message::field(Message::Field::PARAMETERS, message);
kDebug() << "parameters byte array: " << parametersByteArray.toBase64();
QBuffer buffer(&parametersByteArray);
buffer.open(QIODevice::ReadOnly);
QDataStream in(&buffer);
QMap<QString, QVariant> parameters;
in >> parameters;
if (!description.isValid()) {
kDebug() << "invalid description.";
}
kDebug() << "====PARAMETERS====";
//write the parameters into the operation description
foreach (const QString &key, parameters.keys()) {
kDebug() << "key = " << key << ", value = " << parameters.value(key);
description.writeEntry(key, parameters.value(key));
}
m_service->setDestination(Message::field(Message::Field::DESTINATION, message));
ServiceJob *job = m_service->startOperationCall(description);
QString identityID = Message::field(Message::Field::IDENTITYID, message);
job->d->identity = AuthorizationManager::self()->d->getCredentials(identityID);
kDebug() << "adding into messagemap:" << ((QObject*)job);
m_messageMap[job] = message;
}
void ServiceProvider::sendOperations(Jolie::Message message)
{
kDebug() << "send operations.";
//kDebug() << printJolieMessage(message);
Jolie::Message response(message.resourcePath(), message.operationName(), message.id());
//FIXME: this is duplicated from Plasma::Service
QString path = KStandardDirs::locate("data", "plasma/services/" + m_service->name() +
".operations");
if (path.isEmpty()) {
kDebug() << "Cannot find operations description:" << m_service->name() << ".operations";
response.setFault(Jolie::Fault("NoOperationsDescription"));
} else {
kDebug() << "file = " << path;
QFile file(path);
file.open(QIODevice::ReadOnly);
Jolie::Value value;
value.children(Message::Field::OPERATIONSDESCRIPTION) << Jolie::Value(file.readAll());
file.close();
response.setData(value);
}
QByteArray id = Message::field(Message::Field::IDENTITYID, message);
QByteArray uuid = Message::field(Message::Field::UUID, message);
response = appendToken(response, id, uuid);
kDebug() << "caller = " << id.toBase64();
//hack around the not yet async service adaptor api in qtjolie
if (m_descriptorMap.contains(id + uuid)) {
kDebug() << "descriptor found, sending message";
AuthorizationManager::self()->d->server->sendReply(
m_descriptorMap.value(id + uuid), response);
} else {
kDebug() << "no valid entry in descriptormap.";
}
}
void ServiceProvider::sendEnabledOperations(Jolie::Message message)
{
kDebug() << "send enabled operations.";
Jolie::Message response(message.resourcePath(), message.operationName(), message.id());
QStringList enabledOperationsList;
foreach (const QString &operation, m_service->operationNames()) {
if (m_service->isOperationEnabled(operation)) {
enabledOperationsList << operation;
}
}
kDebug() << "enabled operations: " << enabledOperationsList;
QByteArray enabledOperationsArray;
QDataStream out(&enabledOperationsArray, QIODevice::WriteOnly);
out << enabledOperationsList;
Jolie::Value value;
value.children(Message::Field::ENABLEDOPERATIONS) << Jolie::Value(enabledOperationsArray);
response.setData(value);
QByteArray id = Message::field(Message::Field::IDENTITYID, message);
QByteArray uuid = Message::field(Message::Field::UUID, message);
response = appendToken(response, id, uuid);
kDebug() << "caller = " << id.toBase64();
//hack around the not yet async service adaptor api in qtjolie
if (m_descriptorMap.contains(id + uuid)) {
kDebug() << "descriptor found, sending message";
AuthorizationManager::self()->d->server->sendReply(
m_descriptorMap.value(id + uuid), response);
} else {
kDebug() << "no valid entry in descriptormap.";
}
}
QString ServiceProvider::resourceName() const
{
return m_providerName;
}
void ServiceProvider::relay(Jolie::Server *server, int descriptor,
const Jolie::Message &message)
{
Q_UNUSED(server)
if (message.operationName() == "startConnection") {
kDebug() << "reset token";
//add the identity
Credentials identity;
QByteArray identityByteArray = Message::field(Message::Field::IDENTITY, message);
QDataStream stream(&identityByteArray, QIODevice::ReadOnly);
stream >> identity;
AuthorizationManager::self()->d->addCredentials(identity);
Jolie::Message response(message.resourcePath(), message.operationName(), message.id());
QByteArray uuid = Message::field(Message::Field::UUID, message);
response = appendToken(response, identity.id().toAscii(), uuid);
AuthorizationManager::self()->d->server->sendReply(descriptor, response);
return;
}
if (Message::field(Message::Field::TOKEN, message).isEmpty()) {
Jolie::Message response(message.resourcePath(), message.operationName(), message.id());
response.setFault(Jolie::Fault(Message::Error::INVALIDTOKEN));
AuthorizationManager::self()->d->server->sendReply(descriptor, response);
return;
}
//m_descriptor = descriptor;
QByteArray id = Message::field(Message::Field::IDENTITYID, message);
QByteArray uuid = Message::field(Message::Field::UUID, message);
m_descriptorMap[id + uuid] = descriptor;
authorize(message, m_tokens[id + uuid]);
}
void ServiceProvider::operationCompleted(Plasma::ServiceJob *job)
{
kDebug() << "operation completed.";
if (!m_messageMap.contains(job)) {
kDebug() << "service not in map!";
return;
}
kDebug() << "found message in message map!";
Jolie::Message message = m_messageMap.take(job);
Jolie::Message response(message.resourcePath(), message.operationName(), message.id());
QVariant variantResult = job->result();
kDebug() << "got a result: " << variantResult;
QByteArray byteArrayResult;
QBuffer buffer(&byteArrayResult);
buffer.open(QIODevice::WriteOnly);
QDataStream out(&buffer);
out << variantResult;
Jolie::Value data;
data.children(Message::Field::RESULT) << Jolie::Value(byteArrayResult);
response.setData(data);
if (job->error()) {
response.setFault(Jolie::Fault(job->errorString().toAscii()));
}
QByteArray id = Message::field(Message::Field::IDENTITYID, message);
QByteArray uuid = Message::field(Message::Field::UUID, message);
response = appendToken(response, id, uuid);
//hack around the not yet async service adaptor api in qtjolie
if (m_descriptorMap.contains(id + uuid)) {
kDebug() << "descriptor found, sending message";
AuthorizationManager::self()->d->server->sendReply(
m_descriptorMap.value(id + uuid), response);
} else {
kDebug() << "no valid entry in descriptormap.";
}
}
void ServiceProvider::ruleChanged(Plasma::AuthorizationRule *rule)
{
int i = 0;
foreach (Jolie::Message message, m_messagesPendingAuthorization) {
QByteArray id = Message::field(Message::Field::IDENTITYID, message);
//Credentials identity = AuthorizationManager::self()->d->getCredentials(id);
bool matches = rule->d->matches(message.resourcePath(), id);
if (matches && rule->policy() == AuthorizationRule::PinRequired &&
Message::field(Message::Field::PIN, message) != rule->pin()) {
kDebug() << "we need a pin";
authorizationFailed(message, Message::Error::REQUIREPIN);
m_messagesPendingAuthorization.removeAt(i);
return;
/**
} else if (matches && rule->policy() == AuthorizationRule::PinRequired) {
kDebug() << "AUTHORIZATION: Service is freely accessable for verified caller.";
rule->setPolicy(AuthorizationRule::Allow);
authorizationSuccess(message);
//TODO: it might be nicer to do a removeAll once Jolie::Message implements ==
m_messagesPendingAuthorization.removeAt(i);
return;
*/
} else if (matches && rule->policy() == AuthorizationRule::Allow) {
kDebug() << "AUTHORIZATION: Service is freely accessable for verified caller.";
authorizationSuccess(message);
//TODO: it might be nicer to do a removeAll once Jolie::Message implements ==
m_messagesPendingAuthorization.removeAt(i);
return;
} else if (matches && rule->policy() == AuthorizationRule::Deny) {
kDebug() << "AUTHORIZATION: Service is never accessable for verified caller.";
authorizationFailed(message, Message::Error::ACCESSDENIED);
m_messagesPendingAuthorization.removeAt(i);
return;
} else {
i++;
}
}
}
Jolie::Message ServiceProvider::appendToken(Jolie::Message message,
const QByteArray &caller,
const QByteArray &uuid)
{
#ifdef ENABLE_REMOTE_WIDGETS
m_tokens[caller + uuid] = QCA::Random::randomArray(256).toByteArray();
#endif
//kDebug() << "setting token: " << m_tokens[caller + uuid].toBase64()
//<< " for caller: " << caller.toBase64()
//<< " with uuid caller: " << uuid.toBase64();
Jolie::Value data = message.data();
data.children(Message::Field::TOKEN) << Jolie::Value(m_tokens[caller + uuid]);
message.setData(data);
return message;
}
void ServiceProvider::authorize(const Jolie::Message &message, const QByteArray &validToken)
{
kDebug() << "VALIDATING MESSAGE:";
//kDebug() << Message::print(message);
//Authorization step 1: is the service accessable to all callers? In that case we can skip the
//verification of the signature
kDebug() << "STEP1";
AuthorizationRule *rule =
AuthorizationManager::self()->d->matchingRule(message.resourcePath(), Credentials());
if (rule && rule->policy() == AuthorizationRule::Allow) {
kDebug() << "AUTHORIZATION: Service is freely accessable.";
authorizationSuccess(message);
return;
} else if (rule && rule->policy() == AuthorizationRule::Deny) {
kDebug() << "AUTHORIZATION: Service is never accessable.";
authorizationFailed(message, Message::Error::ACCESSDENIED);
return;
}
//Authorization step 2: see if the token matches. If it doesn't we can't safely identify the
//caller and are finished.
kDebug() << "STEP2";
if (Message::field(Message::Field::TOKEN, message) != validToken && !validToken.isEmpty()) {
kDebug() << "AUTHORIZATION: Message token doesn't match.";
kDebug() << "expected: " << validToken.toBase64();
authorizationFailed(message, Message::Error::INVALIDTOKEN);
return;
}
QByteArray payload = Message::payload(message);
QByteArray signature = Message::field(Message::Field::SIGNATURE, message);
Credentials identity = AuthorizationManager::self()->d->getCredentials(
Message::field(Message::Field::IDENTITYID, message));
if (!identity.isValid()) {
kDebug() << "no identity";
authorizationFailed(message, Message::Error::INVALIDTOKEN);
return;
}
kDebug() << "STEP3";
//Authorization step 3: see if we have the key and can validate the signature. If we can't,
//either the public key has changed, or somebody is doing something nasty, and we're finished.
if ((!identity.isValidSignature(signature, payload))) {
kDebug() << "AUTHORIZATION: signature invalid.";
authorizationFailed(message, Message::Error::ACCESSDENIED);
return;
}
kDebug() << "STEP4";
//Authorization step 4: if we have a valid signature, see if we've got a matching rule
rule = AuthorizationManager::self()->d->matchingRule(message.resourcePath(), identity);
if (rule && rule->policy() == AuthorizationRule::PinRequired) {
kDebug() << "we expect a pin!";
QByteArray pin = Message::field(Message::Field::PIN, message);
if (rule->pin() == QString(pin)) {
authorizationSuccess(message);
rule->setPolicy(AuthorizationRule::Allow);
} else {
authorizationFailed(message, Message::Error::ACCESSDENIED);
AuthorizationManager::self()->d->rules.removeAll(rule);
delete rule;
}
} else if (rule && rule->policy() == AuthorizationRule::Allow) {
kDebug() << "AUTHORIZATION: Service is freely accessable for validated sender.";
authorizationSuccess(message);
return;
} else if (rule && rule->policy() == AuthorizationRule::Deny) {
kDebug() << "AUTHORIZATION: Service is not accessable for validated sender.";
authorizationFailed(message, Message::Error::ACCESSDENIED);
return;
} else {
//- let the shell set the rule matching this request:
kDebug() << "STEP6";
kDebug() << "leave it up to the authorization interface";
m_messagesPendingAuthorization << message;
AuthorizationRule *newRule =
new AuthorizationRule(QString(message.resourcePath()), identity.id());
connect(newRule, SIGNAL(changed(Plasma::AuthorizationRule*)), this,
SLOT(ruleChanged(Plasma::AuthorizationRule*)));
AuthorizationManager::self()->d->rules.append(newRule);
AuthorizationManager::self()->d->authorizationInterface->authorizationRequest(*newRule);
}
}
void ServiceProvider::authorizationSuccess(const Jolie::Message &message)
{
kDebug() << "message with operationName " << message.operationName() << " allowed!";
//would be lovely if this kind of stuff could be autogenerated code from xml like in dbus
//adaptors
if (message.operationName() == "getOperations") {
sendOperations(message);
} else if (message.operationName() == "getEnabledOperations") {
sendEnabledOperations(message);
} else if (message.operationName() == "startOperationCall") {
startOperationCall(message);
}
}
void ServiceProvider::authorizationFailed(const Jolie::Message &message, const QByteArray &error)
{
kDebug() << "message with operationName " << message.operationName() << " NOT allowed!";
Jolie::Message response(message.resourcePath(), message.operationName(), message.id());
response.setFault(Jolie::Fault(error));
QByteArray id = Message::field(Message::Field::IDENTITYID, message);
QByteArray uuid = Message::field(Message::Field::UUID, message);
AuthorizationManager::self()->d->server->sendReply(
m_descriptorMap.value(id + uuid), response);
return;
}
} //namespace Plasma
#include "serviceprovider_p.moc"