Hello all,

With remote services, dataengines and pin pairing working now, another round 
of api review seemed useful. Things are becoming pretty concrete now. See the 
attached file for all new public classes. Not all these classes need to be in 
public api though. Three are convenience classes of which I'm not yet 100% 
sure if they should be included. These are:

* DenyAllAuthorization & PinPairingAuthorization
Both are implementations of AuthorizationInterface. Since not setting an 
AuthorizationInterface in your shell is potentially unsafe (a plugin can then 
do so), DenyAllAuthorization would avoid every shell, even those not 
interested in remote plasma stuff, needing to implement this interface. 
PinPairingAuthorization will fire a simple dialog for pin pairing. With these 
two default implementations most shells (except a future headless plasma 
shell) will have an implementation that suits their need.

* PinPairingDialog
A dialog that asks for a pin for pin pairing. This is used by 
PinPairingAuthorization.  This could be made private, but having it public 
might avoid some duplication in case we want to hide this dialog behind, say, 
a knotification.

The prettiest solution for pin pairing in plasma-desktop would be having a 
knotfication like notification in the systray, with a line edit in the 
notification I think. This would need a protocol in the systray specific for 
this kind of notifications. So this isn't my primary focus, but it would be 
nice if we would add that at some point.

About AccessManager:
accessService was put in AccessManager since accessing a service is async and 
making it a static in Service would make it a bit inconsistent to return a 
KJob that allows access to the service. serviceForSource in dataengine however 
returns a Service directly, so to make that work I needed to add a 
serviceReady signal to Service anyway. ServiceAccessJob isn't really necesarry 
anymore. So I'm thinking of making accessService static in Service. 
Disadvantage is that for applets we'll still need to return a job because we 
can't really instantiate an applet without having obtained it first. So access 
to applets still need a place in AccessManager (and so do functions for 
service discovery) I hate it if there are two methods for service access 
though (Service *serviceForSource and ServiceAccessJob *accessService). 
Another option would be to have accessService in AccessManager and have it 
still return a Service *, but that would make it inconsistent with 
accessApplet.

Please ask me if there are things unclear about the current api, and I'll try 
to elaborate.

Regards,
Rob
/**
 * @class AuthorizationInterface plasma/authorizationinterface.h <Plasma/AuthorizationInterface>
 *
 * @short Allows authorization of access to plasma services.
 *
 * This class is only needed when you create a plasma shell. When you implement it and register it
 * with the AuthorizationManager class, it allows you to respond to incoming service access
 * attempts. Whenever a message is received that does not match any of the AuthorizationRules,
 * AuthorizationManager creates a new rule matching it, and passes it to the authorize function.
 * Change the rule from Unspecified to something else like Allow or Deny to continue processing the
 * message.
 *
 * @since 4.4?
 */
class PLASMA_EXPORT AuthorizationInterface
{
    public:
        virtual ~AuthorizationInterface();

        /**
         * implement this function to respond to an incoming request that doesn't match any rule.
         * @param rule a new AuthorizationRule matching an incoming operation. Call setRules on this
         * rule to allow/deny the operation.
         */
        virtual void authorize(AuthorizationRule *rule) = 0;

        /**
         * Implement this function to respond to an outgoing connection that needs a password to
         * connect succesfully. As a response to this you'll probably want to show a dialog.
         * @param request a ClientPinRequest where you can call setPin on to set the pin for the
         * outgoing connection.
         */
        virtual void clientPinRequest(ClientPinRequest *request) = 0;

    protected:
        AuthorizationInterface();
};

/**
 * @class AuthorizationManager plasma/authorizationmanager.h <Plasma/AccessManager>
 *
 * @short Allows authorization of access to plasma services.
 *
 * This is the class where every message to or from another machine passes through. 
 * It's responsibilities are:
 * - creating/keeping a public/private key pair for message signing.
 * - signing and verifying signatures.
 * - testing whether or not the sender is allowed to access the requested resource by testing the
 *   request to a set of rules.
 * - allowing the shell to respond to a remote request that doesn't match any of the 
 *   rules that are in effect.
 * Besides internal use in libplasma, the only moment you'll need to access this class is when you
 * implement a plasma shell. You should alway call setAuthorizationInterface once in a plasma shell.
 *
 * @since 4.4?
 */
class PLASMA_EXPORT AuthorizationManager : public QObject
{
    Q_OBJECT
    public:
        /**
         * Singleton pattern accessor.
         */
        static AuthorizationManager *self();

        /**
         * Register an implementation of AuthorizationInterface. Use this to make your shell
         * handle authorization requests. Note that once an interface can only be set once.
         * This makes sure that plugins can never register another interface.
         * Note that not setting a authorization interface would be considered very unsafe, since
         * that could potentially allow plugins to set one, and authorize everything. If you don't
         * plan on supporting remote access in your shell you should set this to an instance of
         * DenyAllAuthorization.
         */
        static void setAuthorizationInterface(AuthorizationInterface *interface);

    private:
        AuthorizationManager();
        ~AuthorizationManager();

        AuthorizationManagerPrivate *const d;

        friend class AuthorizationManagerSingleton;
        friend class AuthorizationRule;
        friend class Identity;
        friend class RemoteService;
        friend class RemoteServiceJob;
        friend class ServiceProvider;
};

/**
 * @class AuthorizationRule plasma/authorizationrule.h <Plasma/AuthorizationRule>
 *
 * @short Defines a rule indicating whether or not a certain service can be accessed by a certain
 * machine.
 *
 * Rules allow you to have control over which computers are allowed to access which
 * services. Everytime a message get's in, AuthorizationManager validates it's sender, and then
 * checks it's list of rules for rules matching the sender and/or the service. If no rules match,
 * or all matching rules have the value Unspecified, AuthorizationManager will create a new rule
 * for this message, and invoke authorize on your shells implementation of AuthorizationInterface.
 * Here, you can change that rule to either allow or deny that request.
 * This class can be used to specify different types of rules:
 * - Rules matching only a user
 * - Rules matching only a service
 * - Rules matching both a service, and a user.
 * A more specific rule always takes precedence over a more global rule: so if for example you have
 * a rule for "myAwesomeService" specifying Deny, and a rule for "myAwesomeService" in combination
 * with "130.42.120.146" as caller specifying Allow, only 130.42.120.146 can access
 * myAwesomeService.
 * By setting the PinRequired flag in setRules in an AuthorizationInterface implementation, you
 * trigger Pin pairing (user will be asked to enter the same password on both machines).
 *
 * @since 4.4?
 */
class PLASMA_EXPORT AuthorizationRule : public QObject
{
    Q_OBJECT
    public:
        /**
         * Defines this rule's behavior. 
         */
        enum Rule {
            Unspecified = 0,    /**< this rule doesn't specify if the access is allowed or not. */
            Deny = 1,           /**< access for messages matching this rule is denied. */
            Allow = 2,          /**< access for messages matching this rule is allowed. */
            AllUsers = 4,       /**< specify that this rule is valid for all users */
            AllServices = 8,    /**< specify that this rule is valid for all services */
            PinRequired = 16,   /**< specify that the user will need to enter a pin at both sides */
            DefaultRule = Unspecified
        };
        Q_DECLARE_FLAGS(Rules, Rule)

        /**
         * @returns a friendly and i18n'd description of the current rule, useful for creating a
         * GUI to allow editing rules, or asking permission for an access attempt.
         */
        QString description() const;

        /**
         * @returns whether or not this rule matches a certain message.
         */
        bool matches(const QString &serviceName, const Identity *key) const;

        /**
         * @param rules the flags describing this rule.
         */
        void setRules(Rules rules);

        /**
         * @returns the flags describing this rule.
         */
        Rules rules();

        /**
         * @param pin set pin for pin pairing. You'll need to call this bevore setting the rule.
         */
        void setPin(const QString &pin);

        /**
         * @returns the pin for pin pairing.
         */
        QString pin() const;

        /**
         * @returns the identity of the caller.
         */
        Identity *identity() const;

        /**
         * @returns the name of the service this rule applies to.
         */
        QString serviceName() const;

    private:
        AuthorizationRule(const QString &serviceName, Identity *identity);
        ~AuthorizationRule();

        AuthorizationRulePrivate * const d;

        friend class AuthorizationManager;
        friend class AuthorizationManagerPrivate;
};

/**
 * @class AccessManager plasma/accessmanager.h <Plasma/AccessManager>
 *
 * @short Allows access to remote Plasma::Service, Plasma::DataEngine and Plasma::Applet classes.
 *
 * This manager provides a way to access a Plasma::Service, Plasma::DataEngine or Plasma::Applet
 * that is hosted on another machine. It also provides a mechanism to discover services announced 
 * to the network through zeroconf or bluetooth.
 * All url's passed to the access functions need to be valid JOLIE urls, that have this format:
 * plasma://<hostname/ip>:<port>/!/<servicename>
 * All access function are asynchronous. The services need to be accessed over the network, and
 * might even have to be authorized by the user of that machine first. All access functions
 * therefore return a job that can be monitored to see when the service is ready for use.
 *
 * @since 4.4?
 */

class PLASMA_EXPORT AccessManager : public QObject
{
    Q_OBJECT

    public:
        /**
         * Singleton pattern accessor.
         */
        static AccessManager *self();

        /**
         * Access a native Plasma::Service hosted on another machine.
         *
         * @param location a valid JOLIE url
         * @returns a job that can be monitored to see when access to the remote service is
         * obtained, or if it failed.
         */
        ServiceAccessJob* accessService(KUrl location) const;

        /**
         * TODO: I think there should be a more elegant way to access SOAP services right? Plus if
         * we want this to work with RemoteService, the JOLIE script is required to have the exact
         * native plasma service interface.... which means, amonst others: provide an 
         * operationsDescription operation which returns valid ConfigXml. This way of accessing is
         * easy enough, but I fear the creation of the jolieScript will become more complicated then
         * it needs to be.... or we need to have some utility in the feature that automagically
         * creates JOLIE script from WSDL... which would be totally awesome.
         * Create a Plasma::Service that accesses a not native Plasma::Service like a SOAP service. 
         * To accomplish this you'll need to provide the name of a JOLIE script that accesses this 
         * service and has a valid interface (TODO: I'll need to provide a include for jolie scripts
         * for this) and optionally a map that will be passed to the JOLIE script's init function,
         * and can contain things like username/password, url etc.
         * @param jolieScript filename of the jolie script. TODO: which path's to look?
         * @param initValues map of strings>variants that will get passed to the jolie script's init
         * function.
         * @returns a job that can be monitored to see when access to the remote service is
         * obtained, or if it failed.
         */
        ServiceAccessJob* accessService(const QString &jolieScript, 
                                        const QMap<QString, QVariant> &initValues) const;


        //TODO: access functions for engines and applets... which are higher level things built on
        //top of Plasma::Service, which means I'll first need to get services working and
        //everything.

        //TODO: functions for service discovery through bluetooth and zeroconf

    Q_SIGNALS:
        void serviceAccessFinished(Plasma::ServiceAccessJob*);

    private:
        AccessManager();
        ~AccessManager();

        AccessManagerPrivate * const d;

        Q_PRIVATE_SLOT(d, void slotJobFinished(KJob*))

        friend class AccessManagerPrivate;
        friend class AccessManagerSingleton;
};

/**
 * @class ClientPinRequest plasma/clientpinrequest.h <Plasma/ClientPinRequest>
 *
 * describes an outgoing connection.
 *
 * @since 4.4?
 */
class PLASMA_EXPORT ClientPinRequest : public QObject
{
    Q_OBJECT
    public:
        /**
         * @returns nice i18n'ed description of this outgoing connection.
         */
        QString description() const;

        /**
         * @param pin set a pin for pin pairing.
         */
        void setPin(const QString &pin);

        /**
         * @returns the pin for pin pairing.
         */
        QString pin() const;

    Q_SIGNALS:
        /**
         * Emitted when a pin is set.
         */
        void changed(Plasma::ClientPinRequest*);

    private:
        ClientPinRequest(RemoteService *service);
        ~ClientPinRequest();
        
        ClientPinRequestPrivate * const d;

        friend class RemoteService;

};

/**
 * @class DenyAllAuthorization plasma/denyallauthorization.h <Plasma/DenyAllAuthorization>
 *
 * @short Implementation of AuthorizationInterface that you can use if you don't want to allow
 * any remote access in your shell.
 *
 * @since 4.4?
 */
class PLASMA_EXPORT DenyAllAuthorization : public AuthorizationInterface
{
    public:
        DenyAllAuthorization();
        ~DenyAllAuthorization();
        virtual void authorize(AuthorizationRule *rule);
        virtual void clientPinRequest(ClientPinRequest *request);
};

/**
 * @class PinPairingAuthorization plasma/pinpairingauthorization.h <Plasma/PinPairingAuthorization>
 *
 * @short Implementation of AuthorizationInterface that you can use if you want to use standard
 * pin pairing authorization (let the user type the same password at both sides) in your shell for
 * every rule that doesn't match.
 *
 * @since 4.4?
 */
class PLASMA_EXPORT PinPairingAuthorization : public AuthorizationInterface
{
    public:
        PinPairingAuthorization();
        ~PinPairingAuthorization();
        virtual void authorize(AuthorizationRule *rule);
        virtual void clientPinRequest(ClientPinRequest *request);
};

class PLASMA_EXPORT PinPairingDialog : public QObject
{

Q_OBJECT
    
public:
    PinPairingDialog(AuthorizationRule *rule, QObject* parent = 0);
    PinPairingDialog(ClientPinRequest *request, QObject* parent = 0);
    ~PinPairingDialog();

private:
    PinPairingDialogPrivate *const d;

    Q_PRIVATE_SLOT(d, void slotAccept());
    Q_PRIVATE_SLOT(d, void slotReject());

    friend class PinPairingDialogPrivate;
};

/**
 * @class Identity plasma/identity.h <Plasma/Identity>
 *
 * This class encapsules someone's identity.
 * It contains a unique id that identifies the machine an incoming connection is coming from, it's
 * name (which is not necesarily unique and/or trusted), a public key used to validate messages
 * coming from the machine with this identity, and in the future the possibility to determine
 * whether or not this identity can be trusted based on mechanisms different then pin pairing, e.g.
 * a signature of the key that can be verified by a gpg trusted key.
 */
class Identity : public QObject
{
    Q_OBJECT
    
public:
    ~Identity();

    /**
     * @return whether or not this identity can be trusted based on e.g. having the key signed with
     * a trusted GPG key (not yet implemented) or having the key in a designated folder on disk
     * (about to be impl.). If this function returns false, your shell should always instatiate
     * pin pairing before allowing a connection from an untrusted source
     * (AuthorizationRule::PinRequired flag should be set on the rule with setRules).
     */
    bool isTrusted() const;

    /**
     * @return the name of this identity. There's however no guarantee that if the name returns e.g.
     * "Santa Claus", this message is actually from Mr. Claus, except if isTrusted is true.
     */
    QString name() const;

    /**
     * @return an id to identify this identity. I use a Hash of the public key as ID. This way we
     * don't have to send the complete public key with every message.
     */
    QString id() const;

    /**
     * @return wheter or not @p signature is correct for @p message.
     */
    bool isValidSignature(const QByteArray &signature, const QByteArray &message) const;

private:
    Identity(const QString &id, const QString &name,
             const QCA::PublicKey &key, QObject* parent = 0);
             
    IdentityPrivate *const d;
    friend class AuthorizationManagerPrivate;
    
};

class PLASMA_EXPORT Service : public QObject
{
public:
    void publish();
    
    /**
     * @return the name of the resource when this service is published. name() is not really an option,
     * since when publishing multiple dataengines we would end up with dataengineservice0 
     * dataengineservice1 etc. while plasma_dataengine_nowplaying etc. make more sense. Same for published
     * applets. I'm thinking now that instead of this a parameter in publish might be cleaner though.
     */
    QString resourceName() const;

protected:
    void setResourceName(const QString &name);
};

class PLASMA_EXPORT DataEngine : public QObject
{
public:
    void publish();
};
_______________________________________________
Plasma-devel mailing list
[email protected]
https://mail.kde.org/mailman/listinfo/plasma-devel

Reply via email to