./0000755000004100000410000000000013071130602011234 5ustar www-datawww-data./common-vars.pri0000644000004100000410000000141613071130602014213 0ustar www-datawww-data#----------------------------------------------------------------------------- # Common variables for all projects. #----------------------------------------------------------------------------- #----------------------------------------------------------------------------- # Project name (used e.g. in include file and doc install path). # remember to update debian/* files if you changes this #----------------------------------------------------------------------------- PROJECT_NAME = accounts-qml-module #----------------------------------------------------------------------------- # Project version # remember to update debian/* files if you changes this #----------------------------------------------------------------------------- PROJECT_VERSION = 0.6 # End of File ./common-project-config.pri0000644000004100000410000000065413071130602016154 0ustar www-datawww-data#----------------------------------------------------------------------------- # Common configuration for all projects. #----------------------------------------------------------------------------- # we don't like warnings... QMAKE_CXXFLAGS += -Werror -Wno-write-strings # Disable RTTI QMAKE_CXXFLAGS += -fno-exceptions -fno-rtti TOP_SRC_DIR = $$PWD TOP_BUILD_DIR = $${TOP_SRC_DIR}/$${BUILD_DIR} include(coverage.pri) ./COPYING0000644000004100000410000006350213071130602012275 0ustar www-datawww-data GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! ./src/0000755000004100000410000000000013071130602012023 5ustar www-datawww-data./src/src.pro0000644000004100000410000000352113071130602013335 0ustar www-datawww-datainclude(../common-project-config.pri) TEMPLATE = lib TARGET = Accounts API_URI = "Ubuntu.OnlineAccounts" DESTDIR = $$replace(API_URI, \\., /) CONFIG += \ link_pkgconfig \ plugin \ qt QT += qml # Error on undefined symbols QMAKE_LFLAGS += $$QMAKE_LFLAGS_NOUNDEF PKGCONFIG += \ accounts-qt5 \ libsignon-qt5 CONFIG(debug) { DEFINES += \ DEBUG_ENABLED } SOURCES += \ account-service-model.cpp \ account-service.cpp \ account.cpp \ application-model.cpp \ application.cpp \ credentials.cpp \ debug.cpp \ manager.cpp \ plugin.cpp \ provider-model.cpp HEADERS += \ account-service-model.h \ account-service.h \ account.h \ application-model.h \ application.h \ credentials.h \ debug.h \ manager.h \ plugin.h \ provider-model.h DEFINES += API_URI=\\\"$${API_URI}\\\" qmldir_gen.input = qmldir.in qmldir_gen.output = $${DESTDIR}/qmldir QMAKE_SUBSTITUTES += qmldir_gen OTHER_FILES += qmldir.in PLUGIN_INSTALL_BASE = $$[QT_INSTALL_QML]/$$replace(API_URI, \\., /) target.path = $${PLUGIN_INSTALL_BASE} INSTALLS += target qmldir.files = $${DESTDIR}/qmldir qmldir.path = $${PLUGIN_INSTALL_BASE} INSTALLS += qmldir generateQmlTypes.output = $${DESTDIR}/plugin.qmltypes generateQmlTypes.input = QML_PLUGINS generateQmlTypes.commands = export LD_PRELOAD=${QMAKE_FILE_IN}; $$[QT_INSTALL_BINS]/qmlplugindump -notrelocatable $${API_URI} 0.1 . > ${QMAKE_FILE_OUT} generateQmlTypes.name = Generate ${QMAKE_FILE_OUT} generateQmlTypes.CONFIG += no_link generateQmlTypes.variable_out = QML_TYPES QMAKE_EXTRA_COMPILERS += generateQmlTypes QML_PLUGINS += $${DESTDIR}/lib$${TARGET}.so qmltypes.path = $${PLUGIN_INSTALL_BASE} qmltypes.files = $${DESTDIR}/plugin.qmltypes qmltypes.depends = $${DESTDIR}/plugin.qmltypes qmltypes.CONFIG += no_check_exist INSTALLS += qmltypes ./src/application-model.h0000644000004100000410000000415613071130602015603 0ustar www-datawww-data/* * Copyright (C) 2013 Canonical Ltd. * * Contact: Alberto Mardegan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef ONLINE_ACCOUNTS_APPLICATION_MODEL_H #define ONLINE_ACCOUNTS_APPLICATION_MODEL_H #include "manager.h" #include #include #include #include namespace OnlineAccounts { class Application; class ApplicationModel: public QAbstractListModel { Q_OBJECT Q_PROPERTY(int count READ rowCount NOTIFY countChanged) Q_PROPERTY(QString service READ service WRITE setService \ NOTIFY serviceChanged) public: ApplicationModel(QObject *parent = 0); ~ApplicationModel(); enum Roles { ApplicationIdRole = Qt::UserRole + 1, DisplayNameRole, IconNameRole, ServiceUsageRole, ApplicationRole, TranslationsRole, }; void setService(const QString &serviceId); QString service() const; Q_INVOKABLE QVariant get(int row, const QString &roleName) const; // reimplemented virtual methods int rowCount(const QModelIndex &parent = QModelIndex()) const; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; QHash roleNames() const; Q_SIGNALS: void countChanged(); void serviceChanged(); private: void computeApplicationList(); private: QSharedPointer manager; QList applications; Accounts::Service m_service; }; }; // namespace #endif // ONLINE_ACCOUNTS_APPLICATION_MODEL_H ./src/plugin.cpp0000644000004100000410000000353613071130602014034 0ustar www-datawww-data/* * Copyright (C) 2013 Canonical Ltd. * * Contact: Alberto Mardegan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "account-service-model.h" #include "account-service.h" #include "account.h" #include "application-model.h" #include "credentials.h" #include "debug.h" #include "manager.h" #include "plugin.h" #include "provider-model.h" #include #include using namespace OnlineAccounts; static QObject *createManager(QQmlEngine *engine, QJSEngine *scriptEngine) { Q_UNUSED(engine); Q_UNUSED(scriptEngine); return new Manager(); } void Plugin::registerTypes(const char *uri) { QByteArray loggingLevelVar = qgetenv("OAQ_LOGGING_LEVEL"); if (!loggingLevelVar.isEmpty()) { setLoggingLevel(loggingLevelVar.toInt()); } DEBUG() << Q_FUNC_INFO << uri; qmlRegisterType(uri, 0, 1, "AccountServiceModel"); qmlRegisterType(uri, 0, 1, "AccountService"); qmlRegisterType(uri, 0, 1, "Account"); qmlRegisterType(uri, 0, 1, "ApplicationModel"); qmlRegisterType(uri, 0, 1, "Credentials"); qmlRegisterType(uri, 0, 1, "ProviderModel"); qmlRegisterSingletonType(uri, 0, 1, "Manager", createManager); } ./src/account.h0000644000004100000410000000537713071130602013644 0ustar www-datawww-data/* * Copyright (C) 2013 Canonical Ltd. * * Contact: Alberto Mardegan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef ONLINE_ACCOUNTS_ACCOUNT_H #define ONLINE_ACCOUNTS_ACCOUNT_H #include #include #include #include namespace Accounts { class Account; class AccountService; }; namespace SignOn { class Identity; }; namespace OnlineAccounts { class Account: public QObject { Q_OBJECT Q_PROPERTY(QObject *objectHandle READ objectHandle \ WRITE setObjectHandle NOTIFY objectHandleChanged) Q_PROPERTY(bool enabled READ enabled NOTIFY enabledChanged) Q_PROPERTY(QVariantMap provider READ provider NOTIFY objectHandleChanged) Q_PROPERTY(QString displayName READ displayName NOTIFY displayNameChanged) Q_PROPERTY(uint accountId READ accountId NOTIFY accountIdChanged) Q_PROPERTY(QObject *accountServiceHandle READ accountServiceHandle \ NOTIFY objectHandleChanged) public: enum RemovalOption { RemoveAccountOnly = 0x0, RemoveCredentials = 0x1, }; Q_DECLARE_FLAGS(RemovalOptions, RemovalOption) Q_FLAGS(RemovalOption RemovalOptions) Account(QObject *parent = 0); ~Account(); void setObjectHandle(QObject *object); QObject *objectHandle() const; bool enabled() const; QVariantMap provider() const; QString displayName() const; uint accountId() const; QObject *accountServiceHandle() const; Q_INVOKABLE void updateDisplayName(const QString &displayName); Q_INVOKABLE void updateEnabled(bool enabled); Q_INVOKABLE void sync(); Q_INVOKABLE void remove(RemovalOptions options = RemoveCredentials); Q_SIGNALS: void objectHandleChanged(); void accountIdChanged(); void enabledChanged(); void displayNameChanged(); void synced(); void removed(); private Q_SLOTS: void onRemoved(); void onIdentityRemoved(); private: QPointer account; QPointer accountService; QList identities; }; }; // namespace Q_DECLARE_OPERATORS_FOR_FLAGS(OnlineAccounts::Account::RemovalOptions) #endif // ONLINE_ACCOUNTS_ACCOUNT_H ./src/account-service-model.cpp0000644000004100000410000006051313071130602016724 0ustar www-datawww-data/* * Copyright (C) 2013 Canonical Ltd. * * Contact: Alberto Mardegan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "account-service-model.h" #include "debug.h" #include "manager.h" #include #include #include #include #include #include using namespace OnlineAccounts; static const QLatin1String globalService("global"); static bool sortByProviderAndDisplayName(const Accounts::AccountService *as1, const Accounts::AccountService *as2) { const Accounts::Account *a1 = as1->account(); const Accounts::Account *a2 = as2->account(); int diff = QString::compare(a1->providerName(), a2->providerName()); if (diff < 0) return true; if (diff > 0) return false; diff = QString::compare(a1->displayName(), a2->displayName()); if (diff < 0) return true; if (diff > 0) return false; // last, sort by service return as1->service().name() < as2->service().name(); } namespace OnlineAccounts { typedef QList AccountServices; class AccountServiceModelPrivate: public QObject { Q_OBJECT Q_DECLARE_PUBLIC(AccountServiceModel) public: AccountServiceModelPrivate(AccountServiceModel *model); ~AccountServiceModelPrivate(); void queueUpdate(); AccountServices listAccountServices(Accounts::Account *account) const; AccountServices watchAccount(Accounts::Account *account); void addServicesFromAccount(Accounts::Account *account); void watchItems(const AccountServices &items); void addItems(const AccountServices &added); void removeItems(const AccountServices &removed); void sortItems(); public Q_SLOTS: void update(); void onAccountCreated(Accounts::AccountId id); void onAccountRemoved(Accounts::AccountId id); void onAccountDisplayNameChanged(); void onAccountServiceEnabled(bool enabled); private: mutable AccountServiceModel *q_ptr; QHash roleNames; bool componentCompleted; bool updateQueued; bool accountIdChanged; bool accountChanged; bool applicationIdChanged; bool providerChanged; bool serviceTypeChanged; bool serviceChanged; bool includeDisabled; Accounts::AccountId accountId; QPointer account; Accounts::Application application; QString providerId; QString serviceTypeId; QString serviceId; QSharedPointer manager; AccountServices allItems; AccountServices modelItems; bool (*sortFunction)(const Accounts::AccountService *as1, const Accounts::AccountService *as2); }; }; // namespace AccountServiceModelPrivate::AccountServiceModelPrivate(AccountServiceModel *model): QObject(model), q_ptr(model), componentCompleted(false), updateQueued(true), accountIdChanged(false), accountChanged(false), providerChanged(false), serviceTypeChanged(false), serviceChanged(false), includeDisabled(false), accountId(0), sortFunction(sortByProviderAndDisplayName) { } AccountServiceModelPrivate::~AccountServiceModelPrivate() { qDeleteAll(allItems); } void AccountServiceModelPrivate::queueUpdate() { if (updateQueued) return; updateQueued = true; QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection); } AccountServices AccountServiceModelPrivate::listAccountServices(Accounts::Account *account) const { AccountServices ret; if (Q_UNLIKELY(account == 0)) return ret; if (!providerId.isEmpty() && account->providerName() != providerId) return ret; if (serviceId == globalService) { ret.append(new Accounts::AccountService(account, Accounts::Service())); } else { foreach (Accounts::Service service, account->services()) { if (!serviceId.isEmpty() && service.name() != serviceId) continue; if (application.isValid() && application.serviceUsage(service).isEmpty()) continue; ret.append(new Accounts::AccountService(account, service)); } } return ret; } AccountServices AccountServiceModelPrivate::watchAccount(Accounts::Account *account) { AccountServices accountServices = listAccountServices(account); watchItems(accountServices); QObject::connect(account, SIGNAL(displayNameChanged(const QString &)), this, SLOT(onAccountDisplayNameChanged()), Qt::UniqueConnection); return accountServices; } void AccountServiceModelPrivate::addServicesFromAccount(Accounts::Account *account) { AccountServices accountServices = watchAccount(account); AccountServices newModelItems; foreach (Accounts::AccountService *accountService, accountServices) { if (includeDisabled || accountService->enabled()) newModelItems.append(accountService); } qSort(newModelItems.begin(), newModelItems.end(), sortFunction); addItems(newModelItems); } void AccountServiceModelPrivate::watchItems(const AccountServices &items) { foreach (Accounts::AccountService *accountService, items) { QObject::connect(accountService, SIGNAL(enabled(bool)), this, SLOT(onAccountServiceEnabled(bool))); } allItems.append(items); } /* * NOTE: @added must be already sorted! */ void AccountServiceModelPrivate::addItems(const AccountServices &added) { Q_Q(AccountServiceModel); AccountServices newModelItems = modelItems; QModelIndex root; QMap addedIndexes; foreach (Accounts::AccountService *accountService, added) { // Find where the item should be inserted AccountServices::iterator i = qLowerBound(modelItems.begin(), modelItems.end(), accountService, sortFunction); int index = i - modelItems.begin(); addedIndexes[index]++; } // update the list int inserted = 0; for (QMap::const_iterator i = addedIndexes.constBegin(); i != addedIndexes.constEnd(); i++) { int start = i.key(); int count = i.value(); q->beginInsertRows(root, start + inserted, start + inserted + count - 1); for (int j = 0; j < count; j++) { Accounts::AccountService *accountService = added.at(inserted + j); modelItems.insert(start + inserted + j, accountService); } q->endInsertRows(); inserted += count; } } void AccountServiceModelPrivate::removeItems(const AccountServices &removed) { Q_Q(AccountServiceModel); QModelIndex root; QList removedIndexes; foreach (Accounts::AccountService *accountService, removed) { int index = modelItems.indexOf(accountService); if (Q_UNLIKELY(index < 0)) { qWarning() << "Item already deleted!" << accountService; continue; } removedIndexes.append(index); } // sort the indexes from highest to lower, and start updating the list qSort(removedIndexes.begin(), removedIndexes.end(), qGreater()); int first = -1; int last = -1; foreach (int index, removedIndexes) { // Check whether the indexes are contiguous if (index != first - 1) { // if we have a valid range, update the list for that range if (first != -1) { q->beginRemoveRows(root, first, last); for (int i = last; i >= first; i--) modelItems.removeAt(i); q->endRemoveRows(); } // a new range starts last = index; } first = index; } if (first != -1) { q->beginRemoveRows(root, first, last); for (int i = last; i >= first; i--) modelItems.removeAt(i); q->endRemoveRows(); } } void AccountServiceModelPrivate::sortItems() { qSort(modelItems.begin(), modelItems.end(), sortFunction); } void AccountServiceModelPrivate::update() { Q_Q(AccountServiceModel); updateQueued = false; DEBUG(); if (!modelItems.isEmpty()) { q->beginRemoveRows(QModelIndex(), 0, modelItems.count() - 1); modelItems.clear(); q->endRemoveRows(); } qDeleteAll(allItems); allItems.clear(); if (serviceTypeChanged) { if (!manager.isNull()) { QObject::disconnect(manager.data(), 0, this, 0); manager.clear(); } } /* Instantiate a manager, if needed. If the account property is set to a * valid account, we don't need a manager object. */ if (manager.isNull() && account == 0) { if (serviceTypeId.isEmpty()) { manager = SharedManager::instance(); } else { manager = QSharedPointer( new Accounts::Manager(serviceTypeId)); } QObject::connect(manager.data(), SIGNAL(accountCreated(Accounts::AccountId)), this, SLOT(onAccountCreated(Accounts::AccountId))); QObject::connect(manager.data(), SIGNAL(accountRemoved(Accounts::AccountId)), this, SLOT(onAccountRemoved(Accounts::AccountId))); } QList accounts; if (account != 0) { accounts.append(account); } else if (accountId != 0) { Accounts::Account *account = manager->account(accountId); accounts.append(account); } else { foreach (Accounts::AccountId accountId, manager->accountList()) { Accounts::Account *account = manager->account(accountId); accounts.append(account); } } foreach (Accounts::Account *account, accounts) { watchAccount(account); } AccountServices newModelItems; if (includeDisabled) { newModelItems = allItems; } else { foreach (Accounts::AccountService *accountService, allItems) { if (accountService->enabled()) newModelItems.append(accountService); } } if (!newModelItems.isEmpty()) { q->beginInsertRows(QModelIndex(), 0, newModelItems.count() - 1); modelItems = newModelItems; sortItems(); q->endInsertRows(); } accountIdChanged = false; providerChanged = false; serviceTypeChanged = false; serviceChanged = false; } void AccountServiceModelPrivate::onAccountCreated(Accounts::AccountId id) { DEBUG() << id; Accounts::Account *account = manager->account(id); addServicesFromAccount(account); } void AccountServiceModelPrivate::onAccountRemoved(Accounts::AccountId id) { DEBUG() << id; AccountServices removed; foreach (Accounts::AccountService *accountService, allItems) { if (accountService->account()->id() == id) { removed.append(accountService); } } /* Remove the items from the model */ removeItems(removed); /* Last, delete the items */ foreach (Accounts::AccountService *accountService, removed) { allItems.removeOne(accountService); delete accountService; } } void AccountServiceModelPrivate::onAccountDisplayNameChanged() { Q_Q(AccountServiceModel); Accounts::Account *account = qobject_cast(sender()); for (int row = 0; row < modelItems.count(); row++) { Accounts::AccountService *accountService = modelItems[row]; if (accountService->account() == account) { QModelIndex index = q->index(row); q->dataChanged(index, index); } } } void AccountServiceModelPrivate::onAccountServiceEnabled(bool enabled) { Q_Q(AccountServiceModel); Accounts::AccountService *accountService = qobject_cast(sender()); DEBUG() << enabled; int row = modelItems.indexOf(accountService); if (row > 0) { QModelIndex index = q->index(row); q->dataChanged(index, index); } if (!includeDisabled) { /* The item might need to be added or removed from the model */ AccountServices accountServices; accountServices.append(accountService); if (row < 0 && enabled) { addItems(accountServices); } else if (row >= 0 && !enabled) { removeItems(accountServices); } } } /*! * \qmltype AccountServiceModel * \inqmlmodule Ubuntu.OnlineAccounts 0.1 * \ingroup Ubuntu * * \brief A model of the user's Online Accounts * * The AccountServiceModel is a model representing the user's Online Accounts * services. * Please note that an Online Account can offer several different services * (chat, e-mail, micro-blogging, etc.); these are the items represented by * this model, and not the user accounts as a whole. * Since most applications are interested on a small subset of the user's * accounts, AccountServiceModel offers some filtering functionalities: it is * possible to restrict it to only one account provider, to a specific service * type (for instance, an e-mail application will probably be interested in * only those accounts which offer an e-mail service), or to a specific service * (e.g., picasa; this is often equivalent to filtering by provider and by * service-type, because it's rare for a provider to offer two different * services of the same type). * By default, only enabled accounts are returned. Use the \l includeDisabled * property to list also disabled accounts; keep in mind, though, that an * application should never use an account which has been disabled by the user. * * The model defines the following roles: * \list * \li \c displayName is the name of the account (usually the user's login) * \li \c providerName is the name of the account provider (e.g., "Google") * \li \c serviceName is the name of the service (e.g., "Picasa") * \li \c enabled * \li \c accountServiceHandle is a handle to the underlying Qt object which * can be used to instantiate an \l AccountService from QML * \li \c accountId is the numeric ID of the account * \li \c accountHandle is a handle to the underlying Qt object which can be * used to instantiate an \l Account from QML * \endlist * * Examples of use: * * 1. Model of all enabled microblogging accounts: * \qml * Item { * AccountServiceModel { * id: accounts * serviceType: "microblogging" * } * * ListView { * model: accounts * delegate: Text { text: model.displayName + " by " + model.providerName } * } * } * \endqml * * 2. List all Facebook account services: * \qml * Item { * AccountServiceModel { * id: accounts * provider: "facebook" * includeDisabled: true * } * * ListView { * model: accounts * delegate: Text { text: model.serviceName + " on " + model.displayName } * } * } * \endqml * * 3. List all Flickr accounts enabled for uploading: * \qml * Item { * AccountServiceModel { * id: accounts * service: "flickr-sharing" * } * * ListView { * model: accounts * delegate: Rectangle { * id: rect * * Text { text: rect.model.displayName } * * AccountService { * id: accountService * objectHandle: rect.model.accountServiceHandle * * onAuthenticated: { console.log("Access token is " + reply.AccessToken) } * onAuthenticationError: { console.log("Authentication failed, code " + error.code) } * } * * MouseArea { * anchors.fill: parent * onClicked: accountService.authenticate() * } * } * } * } * \endqml * * 4. List all the online accounts, without their services: * \qml * Item { * AccountServiceModel { * id: accounts * service: "global" * } * * ListView { * model: accounts * delegate: Rectangle { * id: rect * * Text { text: account.displayName } * * Account { * id: account * objectHandle: rect.model.accountHandle * } * } * } * } * \endqml */ AccountServiceModel::AccountServiceModel(QObject *parent): QAbstractListModel(parent), d_ptr(new AccountServiceModelPrivate(this)) { Q_D(AccountServiceModel); d->roleNames[DisplayNameRole] = "displayName"; d->roleNames[ProviderNameRole] = "providerName"; d->roleNames[ServiceNameRole] = "serviceName"; d->roleNames[EnabledRole] = "enabled"; d->roleNames[AccountServiceHandleRole] = "accountServiceHandle"; d->roleNames[AccountServiceRole] = "accountService"; d->roleNames[AccountIdRole] = "accountId"; d->roleNames[AccountHandleRole] = "accountHandle"; d->roleNames[AccountRole] = "account"; QObject::connect(this, SIGNAL(rowsInserted(const QModelIndex &,int,int)), this, SIGNAL(countChanged())); QObject::connect(this, SIGNAL(rowsRemoved(const QModelIndex &,int,int)), this, SIGNAL(countChanged())); } AccountServiceModel::~AccountServiceModel() { delete d_ptr; } void AccountServiceModel::classBegin() { } void AccountServiceModel::componentComplete() { Q_D(AccountServiceModel); d->componentCompleted = true; d->update(); } /*! * \qmlproperty quint32 AccountServiceModel::accountId * If set, the model will list only those accounts services available in the * given account. */ void AccountServiceModel::setAccountId(quint32 accountId) { Q_D(AccountServiceModel); if (accountId == d->accountId) return; d->accountId = accountId; d->accountIdChanged = true; d->queueUpdate(); Q_EMIT accountIdChanged(); } quint32 AccountServiceModel::accountId() const { Q_D(const AccountServiceModel); return d->accountId; } /*! * \qmlproperty Account AccountServiceModel::account * If set, the model will list only those accounts services available in the * given account. */ void AccountServiceModel::setAccount(QObject *object) { Q_D(AccountServiceModel); Accounts::Account *account = qobject_cast(object); if (account == d->account) return; d->account = account; d->accountChanged = true; d->queueUpdate(); Q_EMIT accountChanged(); } QObject *AccountServiceModel::account() const { Q_D(const AccountServiceModel); return d->account; } /*! * \qmlproperty string AccountServiceModel::applicationId * If set, the model will only show those account services which are relevant * for the given \a applicationId. This means that an account service will only * be shown if it can be used by the application, as described in the * application's manifest file. */ void AccountServiceModel::setApplicationId(const QString &applicationId) { Q_D(AccountServiceModel); if (applicationId == d->application.name()) return; if (applicationId.isEmpty()) { d->application = Accounts::Application(); } else { d->application = SharedManager::instance()->application(applicationId); } d->applicationIdChanged = true; d->queueUpdate(); Q_EMIT applicationIdChanged(); } QString AccountServiceModel::applicationId() const { Q_D(const AccountServiceModel); return d->application.name(); } /*! * \qmlproperty string AccountServiceModel::provider * If set, the model will list only those accounts services provided by this provider. */ void AccountServiceModel::setProvider(const QString &providerId) { Q_D(AccountServiceModel); if (providerId == d->providerId) return; d->providerId = providerId; d->providerChanged = true; d->queueUpdate(); Q_EMIT providerChanged(); } QString AccountServiceModel::provider() const { Q_D(const AccountServiceModel); return d->providerId; } /*! * \qmlproperty string AccountServiceModel::serviceType * If set, the model will list only those accounts services supporting this * service type. Each provider-specific service is an instance of a generic * service type (such as "e-mail", "IM", etc.) which identifies the main * functionality provided by the service. */ void AccountServiceModel::setServiceType(const QString &serviceTypeId) { Q_D(AccountServiceModel); if (serviceTypeId == d->serviceTypeId) return; d->serviceTypeId = serviceTypeId; d->serviceTypeChanged = true; d->queueUpdate(); Q_EMIT serviceTypeChanged(); } QString AccountServiceModel::serviceType() const { Q_D(const AccountServiceModel); return d->serviceTypeId; } /*! * \qmlproperty string AccountServiceModel::service * If set, the model will list only those accounts services for this * specific service. */ void AccountServiceModel::setService(const QString &serviceId) { Q_D(AccountServiceModel); if (serviceId == d->serviceId) return; d->serviceId = serviceId; d->serviceChanged = true; d->queueUpdate(); Q_EMIT serviceChanged(); } QString AccountServiceModel::service() const { Q_D(const AccountServiceModel); return d->serviceId; } /*! * \qmlproperty bool AccountServiceModel::includeDisabled * If true, even disabled account services will be listed. Note that an * application should never use a disabled account. * * By default, this property is false. */ void AccountServiceModel::setIncludeDisabled(bool includeDisabled) { Q_D(AccountServiceModel); if (includeDisabled == d->includeDisabled) return; d->includeDisabled = includeDisabled; d->queueUpdate(); Q_EMIT includeDisabledChanged(); } bool AccountServiceModel::includeDisabled() const { Q_D(const AccountServiceModel); return d->includeDisabled; } /*! * \qmlmethod variant AccountServiceModel::get(int row, string roleName) * * Returns the data at \a row for the role \a roleName. */ QVariant AccountServiceModel::get(int row, const QString &roleName) const { int role = roleNames().key(roleName.toLatin1(), -1); return data(index(row), role); } int AccountServiceModel::rowCount(const QModelIndex &parent) const { Q_D(const AccountServiceModel); Q_UNUSED(parent); return d->modelItems.count(); } QVariant AccountServiceModel::data(const QModelIndex &index, int role) const { Q_D(const AccountServiceModel); if (index.row() >= d->modelItems.count()) return QVariant(); Accounts::AccountService *accountService = d->modelItems.at(index.row()); QVariant ret; QObject *object = 0; switch (role) { case Qt::DisplayRole: ret = QString("%1 - %2"). arg(accountService->account()->displayName()). arg(accountService->service().displayName()); break; case DisplayNameRole: ret = accountService->account()->displayName(); break; case ProviderNameRole: { Accounts::Provider provider = accountService->account()->provider(); ret = provider.displayName(); } break; case ServiceNameRole: ret = accountService->service().displayName(); break; case EnabledRole: ret = accountService->enabled(); break; case AccountServiceRole: qWarning("accountService role is deprecated, use accountServiceHandle"); case AccountServiceHandleRole: object = accountService; break; case AccountIdRole: ret = accountService->account()->id(); break; case AccountRole: qWarning("account role is deprecated, use accountHandle"); case AccountHandleRole: object = accountService->account(); break; } if (object) { QQmlEngine::setObjectOwnership(object, QQmlEngine::CppOwnership); ret = QVariant::fromValue(object); } return ret; } QHash AccountServiceModel::roleNames() const { Q_D(const AccountServiceModel); return d->roleNames; } #include "account-service-model.moc" ./src/debug.h0000644000004100000410000000254113071130602013264 0ustar www-datawww-data/* * Copyright (C) 2013-2015 Canonical Ltd. * * Contact: Alberto Mardegan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef ONLINE_ACCOUNTS_DEBUG_H #define ONLINE_ACCOUNTS_DEBUG_H #include #ifdef DEBUG_ENABLED extern int accounts_qml_module_logging_level; static inline bool debugEnabled() { return accounts_qml_module_logging_level >= 2; } static inline bool criticalsEnabled() { return accounts_qml_module_logging_level >= 1; } #define DEBUG() \ if (debugEnabled()) qDebug() #define BLAME() \ if (criticalsEnabled()) qCritical() #else // DEBUG_ENABLED #define DEBUG() while (0) qDebug() #define WARNING() while (0) qDebug() #endif namespace OnlineAccounts { void setLoggingLevel(int level); } #endif // ONLINE_ACCOUNTS_DEBUG_H ./src/debug.cpp0000644000004100000410000000160013071130602013612 0ustar www-datawww-data/* * Copyright (C) 2015 Canonical Ltd. * * Contact: Alberto Mardegan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "debug.h" int accounts_qml_module_logging_level = 1; namespace OnlineAccounts { void setLoggingLevel(int level) { accounts_qml_module_logging_level = level; } } ./src/account-service-model.h0000644000004100000410000000710413071130602016366 0ustar www-datawww-data/* * Copyright (C) 2013 Canonical Ltd. * * Contact: Alberto Mardegan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef ONLINE_ACCOUNTS_ACCOUNT_SERVICE_MODEL_H #define ONLINE_ACCOUNTS_ACCOUNT_SERVICE_MODEL_H #include #include #include namespace OnlineAccounts { class AccountServiceModelPrivate; class AccountServiceModel: public QAbstractListModel, public QQmlParserStatus { Q_OBJECT Q_INTERFACES(QQmlParserStatus) Q_PROPERTY(int count READ rowCount NOTIFY countChanged) Q_PROPERTY(bool includeDisabled READ includeDisabled \ WRITE setIncludeDisabled NOTIFY includeDisabledChanged) Q_PROPERTY(quint32 accountId READ accountId WRITE setAccountId \ NOTIFY accountIdChanged) Q_PROPERTY(QObject *account READ account WRITE setAccount \ NOTIFY accountChanged) Q_PROPERTY(QString applicationId READ applicationId \ WRITE setApplicationId NOTIFY applicationIdChanged) Q_PROPERTY(QString provider READ provider WRITE setProvider \ NOTIFY providerChanged) Q_PROPERTY(QString serviceType READ serviceType WRITE setServiceType \ NOTIFY serviceTypeChanged) Q_PROPERTY(QString service READ service WRITE setService \ NOTIFY serviceChanged) public: AccountServiceModel(QObject *parent = 0); ~AccountServiceModel(); enum Roles { DisplayNameRole = Qt::UserRole + 1, ProviderNameRole, ServiceNameRole, EnabledRole, AccountServiceHandleRole, AccountServiceRole, // deprecated AccountIdRole, AccountHandleRole, AccountRole, // deprecated }; void setIncludeDisabled(bool includeDisabled); bool includeDisabled() const; void setAccountId(quint32 accountId); quint32 accountId() const; void setAccount(QObject *account); QObject *account() const; void setApplicationId(const QString &applicationId); QString applicationId() const; void setProvider(const QString &providerId); QString provider() const; void setServiceType(const QString &serviceTypeId); QString serviceType() const; void setService(const QString &serviceId); QString service() const; Q_INVOKABLE QVariant get(int row, const QString &roleName) const; // reimplemented virtual methods int rowCount(const QModelIndex &parent = QModelIndex()) const; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; QHash roleNames() const; void classBegin(); void componentComplete(); Q_SIGNALS: void countChanged(); void includeDisabledChanged(); void accountIdChanged(); void accountChanged(); void applicationIdChanged(); void providerChanged(); void serviceTypeChanged(); void serviceChanged(); private: AccountServiceModelPrivate *d_ptr; Q_DECLARE_PRIVATE(AccountServiceModel) }; }; // namespace #endif // ONLINE_ACCOUNTS_ACCOUNT_SERVICE_MODEL_H ./src/accounts.qdoc0000644000004100000410000000133113071130602014510 0ustar www-datawww-data/* * Copyright 2013 Canonical Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 3. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ /*! \qmlmodule Ubuntu.OnlineAccounts 0.1 \title Ubuntu OnlineAccounts */ ./src/provider-model.h0000644000004100000410000000432713071130602015132 0ustar www-datawww-data/* * Copyright (C) 2013 Canonical Ltd. * * Contact: Alberto Mardegan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef ONLINE_ACCOUNTS_PROVIDER_MODEL_H #define ONLINE_ACCOUNTS_PROVIDER_MODEL_H #include "manager.h" #include #include #include namespace Accounts { class Provider; }; namespace OnlineAccounts { class ProviderModel: public QAbstractListModel, public QQmlParserStatus { Q_OBJECT Q_INTERFACES(QQmlParserStatus) Q_PROPERTY(QString applicationId READ applicationId \ WRITE setApplicationId NOTIFY applicationIdChanged) Q_PROPERTY(int count READ rowCount NOTIFY countChanged) public: ProviderModel(QObject *parent = 0); ~ProviderModel(); enum Roles { ProviderIdRole = Qt::UserRole + 1, IconNameRole, IsSingleAccountRole, TranslationsRole, }; void setApplicationId(const QString &applicationId); QString applicationId() const; Q_INVOKABLE QVariant get(int row, const QString &roleName) const; // reimplemented virtual methods int rowCount(const QModelIndex &parent = QModelIndex()) const; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; QHash roleNames() const; void classBegin(); void componentComplete(); Q_SIGNALS: void applicationIdChanged(); void countChanged(); private: void update(); private: QSharedPointer manager; QList providers; QString m_applicationId; bool m_componentCompleted; }; }; // namespace #endif // ONLINE_ACCOUNTS_PROVIDER_MODEL_H ./src/application-model.cpp0000644000004100000410000001117713071130602016137 0ustar www-datawww-data/* * Copyright (C) 2013 Canonical Ltd. * * Contact: Alberto Mardegan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "debug.h" #include "application-model.h" #include "application.h" #include #include using namespace OnlineAccounts; /*! * \qmltype ApplicationModel * \inqmlmodule Ubuntu.OnlineAccounts 0.1 * \ingroup Ubuntu * * \brief A model of the applications using online accounts. * * The ApplicationModel is a model representing the applications using online * accounts installed on the system. * * In the current implementation, the model is valid only if its \l * ApplicationModel::service property is set to a valid service ID. * * The model defines the following roles: * \list * \li \c applicationId is the unique identifier of the application * \li \c displayName is the application display name * \li \c iconName is the name of the application icon * \li \c serviceUsage is a description of how the application uses the * service; this is set to a valid value only if the \l * ApplicationModel::service property is set to a valid service ID. * \li \c application is the Application object * \li \c translations, the localization domain for translating the * \c serviceUsage field * \endlist */ ApplicationModel::ApplicationModel(QObject *parent): QAbstractListModel(parent), manager(SharedManager::instance()) { } ApplicationModel::~ApplicationModel() { } /*! * \qmlproperty int ApplicationModel::count * The number of items in the model. */ int ApplicationModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); return applications.count(); } /*! * \qmlproperty string ApplicationModel::service * If set, the model will list only those applications which can use this * specific service. */ void ApplicationModel::setService(const QString &serviceId) { if (serviceId == m_service.name()) return; m_service = manager->service(serviceId); beginResetModel(); qDeleteAll(applications); applications.clear(); computeApplicationList(); endResetModel(); Q_EMIT serviceChanged(); } QString ApplicationModel::service() const { return m_service.name(); } /*! * \qmlmethod variant ApplicationModel::get(int row, string roleName) * * Returns the data at \a row for the role \a roleName. */ QVariant ApplicationModel::get(int row, const QString &roleName) const { int role = roleNames().key(roleName.toLatin1(), -1); return data(index(row), role); } QVariant ApplicationModel::data(const QModelIndex &index, int role) const { if (index.row() < 0 || index.row() >= applications.count()) return QVariant(); Application *application = applications.at(index.row()); QVariant ret; switch (role) { case Qt::DisplayRole: case ApplicationIdRole: ret = application->name(); break; case DisplayNameRole: ret = application->displayName(); break; case IconNameRole: ret = application->iconName(); break; case ServiceUsageRole: ret = application->serviceUsage(m_service); break; case ApplicationRole: QQmlEngine::setObjectOwnership(application, QQmlEngine::CppOwnership); ret = QVariant::fromValue(application); break; case TranslationsRole: ret = application->trCatalog(); break; } return ret; } QHash ApplicationModel::roleNames() const { static QHash roles; if (roles.isEmpty()) { roles[ApplicationIdRole] = "applicationId"; roles[DisplayNameRole] = "displayName"; roles[IconNameRole] = "iconName"; roles[ServiceUsageRole] = "serviceUsage"; roles[ApplicationRole] = "application"; roles[TranslationsRole] = "translations"; } return roles; } void ApplicationModel::computeApplicationList() { if (!m_service.isValid()) return; Q_FOREACH(const Accounts::Application &app, manager->applicationList(m_service)) { applications.append(new Application(app, this)); } } ./src/account-service.cpp0000644000004100000410000004020013071130602015615 0ustar www-datawww-data/* * Copyright (C) 2013 Canonical Ltd. * * Contact: Alberto Mardegan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "account-service.h" #include "credentials.h" #include "debug.h" #include #include #include #include #include using namespace OnlineAccounts; static QVariantMap mergeMaps(const QVariantMap &map1, const QVariantMap &map2) { if (map1.isEmpty()) return map2; if (map2.isEmpty()) return map1; QVariantMap map = map1; //map2 values will overwrite map1 values for the same keys. QMapIterator it(map2); while (it.hasNext()) { it.next(); map.insert(it.key(), it.value()); } return map; } AccountService::ErrorCode errorCodeFromSignOn(int type) { if (type <= 0) return AccountService::NoError; switch (type) { case SignOn::Error::SessionCanceled: case SignOn::Error::TOSNotAccepted: return AccountService::UserCanceledError; case SignOn::Error::PermissionDenied: case SignOn::Error::InvalidCredentials: case SignOn::Error::NotAuthorized: case SignOn::Error::MethodOrMechanismNotAllowed: return AccountService::PermissionDeniedError; case SignOn::Error::NoConnection: case SignOn::Error::Network: return AccountService::NetworkError; case SignOn::Error::Ssl: return AccountService::SslError; case SignOn::Error::UserInteraction: return AccountService::InteractionRequiredError; default: return AccountService::NoAccountError; } } void AccountService::syncIfDesired() { if (m_autoSync) { Accounts::Account *account = accountService->account(); if (Q_UNLIKELY(account == 0)) return; /* If needed, we could optimize this to call account->sync() when * re-entering the main loop, in order to reduce the number or writes. * But this would be better done in the Account class itself (and even * better, in libaccounts-glib). */ account->sync(); } } /*! * \qmltype AccountService * \inqmlmodule Ubuntu.OnlineAccounts 0.1 * \ingroup Ubuntu * * \brief Represents an instance of a service in an Online Accounts * * The AccountService element represents a service within an existing online account. * It can be used to obtain an authentication token to use the service it refers to. * * Currently, an AccountService is valid only if its \a objectHandle property * is set to a value obtained from an AccountServiceModel or an Account. * * See AccountServiceModel's documentation for usage examples. */ AccountService::AccountService(QObject *parent): QObject(parent), accountService(0), identity(0), m_credentials(0), constructed(false), m_autoSync(true) { } AccountService::~AccountService() { } /*! * \qmlproperty object AccountService::objectHandle * An opaque handle to the underlying C++ object. Until the property is set, * the AccountService element is uninitialized. Similarly, if the C++ object is * destroyed (for instance, because the AccountServiceModel which owns it is * destroyed or if the account is deleted), expect the AccountService to become * invalid. */ void AccountService::setObjectHandle(QObject *object) { DEBUG() << object; Accounts::AccountService *as = qobject_cast(object); if (Q_UNLIKELY(as == 0)) return; if (as == accountService) return; accountService = as; QObject::connect(accountService, SIGNAL(changed()), this, SIGNAL(settingsChanged())); QObject::connect(accountService, SIGNAL(enabled(bool)), this, SIGNAL(enabledChanged())); delete identity; identity = 0; Q_EMIT objectHandleChanged(); /* Emit the changed signals for all other properties, to make sure * that all bindings are updated. */ Q_EMIT enabledChanged(); Q_EMIT displayNameChanged(); Q_EMIT settingsChanged(); } QObject *AccountService::objectHandle() const { return accountService; } /*! * \qmlproperty bool AccountService::enabled * This read-only property tells whether the AccountService is enabled. An * application shouldn't use an AccountService which is disabled. */ bool AccountService::enabled() const { if (Q_UNLIKELY(accountService == 0)) return false; return accountService->enabled(); } /*! * \qmlproperty bool AccountService::serviceEnabled * This read-only property tells whether the service is enabled within the * account. This property differs from the \l enabled property in that the * \l enabled property also considers whether the account is enabled, while * this one only reflects the status of the service. Applications shouldn't * rely on the value on this property to decide whether to use the account or * not. * * \sa enabled */ bool AccountService::serviceEnabled() const { if (Q_UNLIKELY(accountService == 0)) return false; return accountService->value("enabled").toBool(); } /*! * \qmlproperty jsobject AccountService::provider * An immutable object representing the provider which provides the account. * The returned object will have at least these members: * \list * \li \c id is the unique identifier for this provider * \li \c displayName * \li \c iconName * \li \c isSingleAccount, \a true if this provider supports creating one * account at most * \li \c translations, the localization domain for translating the provider's * display name * \endlist */ QVariantMap AccountService::provider() const { QVariantMap map; if (Q_UNLIKELY(accountService == 0)) return map; Accounts::Account *account = accountService->account(); if (account == 0) return map; Accounts::Provider provider = account->provider(); map.insert("id", provider.name()); map.insert("displayName", provider.displayName()); map.insert("iconName", provider.iconName()); map.insert("isSingleAccount", provider.isSingleAccount()); map.insert("translations", provider.trCatalog()); return map; } /*! * \qmlproperty jsobject AccountService::service * An immutable object representing the service which this AccountService * instantiates. * The returned object will have at least these members: * \list * \li \c id is the unique identified for this service * \li \c displayName * \li \c iconName * \li \c serviceTypeId identifies the provided service type * \li \c translations, the localization domain for translating the provider's * display name * \endlist */ QVariantMap AccountService::service() const { QVariantMap map; if (Q_UNLIKELY(accountService == 0)) return map; Accounts::Service service = accountService->service(); map.insert("id", service.name()); map.insert("displayName", service.displayName()); map.insert("iconName", service.iconName()); map.insert("serviceTypeId", service.serviceType()); map.insert("translations", service.trCatalog()); return map; } /*! * \qmlproperty string AccountService::displayName * The account's display name (usually the user's login or ID); note that all * AccountService objects which work on the same online account will share the * same display name. */ QString AccountService::displayName() const { if (Q_UNLIKELY(accountService == 0)) return QString(); return accountService->account()->displayName(); } /*! * \qmlproperty string AccountService::accountId * The account's numeric ID; note that all AccountService objects which work on * the same online account will have the same ID. */ uint AccountService::accountId() const { if (Q_UNLIKELY(accountService == 0)) return 0; return accountService->account()->id(); } /*! * \qmlproperty jsobject AccountService::settings * A dictionary of all the account service's settings. This does not * include the authentication settings, which are available from the * AccountService::authData property. */ QVariantMap AccountService::settings() const { QVariantMap map; if (Q_UNLIKELY(accountService == 0)) return map; foreach (const QString &key, accountService->allKeys()) { if (key.startsWith("auth") || key == "enabled") continue; map.insert(key, accountService->value(key)); } return map; } /*! * \qmlproperty jsobject AccountService::authData * An object providing information about the authentication. * The returned object will have at least these members: * \list * \li \c method is the authentication method * \li \c mechanism is the authentication mechanism (a sub-specification of the * method) * \li \c parameters is a dictionary of authentication parameters * \li \c credentialsId is the numeric identified of the credentials in the * secrets storage. See the \l Credentials element for more info. * \endlist */ QVariantMap AccountService::authData() const { QVariantMap map; if (Q_UNLIKELY(accountService == 0)) return map; Accounts::AuthData data = accountService->authData(); map.insert("method", data.method()); map.insert("mechanism", data.mechanism()); map.insert("credentialsId", data.credentialsId()); map.insert("parameters", data.parameters()); return map; } /*! * \qmlproperty bool AccountService::autoSync * This property tells whether the AccountService should invoke the * Account::sync() method whenever updateSettings(), updateDisplayName() or * updateServiceEnabled() are called. * By default, this property is true. */ void AccountService::setAutoSync(bool autoSync) { if (autoSync == m_autoSync) return; m_autoSync = autoSync; Q_EMIT autoSyncChanged(); } bool AccountService::autoSync() const { return m_autoSync; } /*! * \qmlproperty Credentials AccountService::credentials * The credentials used by this account service. This property is meant to be * used only when creating or editing the account, and serves to bind a * credentials record to the account: when the value of the \l * Credentials::credentialsId changes, an update of \l * {authData}{authData.credentialsId} will be queued (and immediately executed * if \l autoSync is \c true). * By default, reading this property returns a null object. */ void AccountService::setCredentials(QObject *credentials) { if (credentials == m_credentials) return; m_credentials = credentials; if (m_credentials != 0) { credentialsIdProperty = QQmlProperty(m_credentials, "credentialsId"); credentialsIdProperty.connectNotifySignal(this, SLOT(onCredentialsIdChanged())); onCredentialsIdChanged(); } else { credentialsIdProperty = QQmlProperty(); } Q_EMIT credentialsChanged(); } QObject *AccountService::credentials() const { return m_credentials; } /*! * \qmlmethod void AccountService::updateServiceEnabled(bool enabled) * * Enables or disables the service within the account configuration. * Since the \l enabled property is the combination of the global account's * enabledness status and the specific service's status, its value might not * change after this method is called. * * \sa enabled, serviceEnabled, autoSync */ void AccountService::updateServiceEnabled(bool enabled) { if (Q_UNLIKELY(accountService == 0)) return; Accounts::Account *account = accountService->account(); if (Q_UNLIKELY(account == 0)) return; account->selectService(accountService->service()); account->setEnabled(enabled); syncIfDesired(); } /*! * \qmlmethod void AccountService::updateSettings(jsobject settings) * * Change some settings. Only the settings which are present in the \a settings * dictionary will be changed; all others settings will not be affected. * To remove a settings, set its value to null. * * \sa autoSync */ void AccountService::updateSettings(const QVariantMap &settings) { if (Q_UNLIKELY(accountService == 0)) return; QMapIterator it(settings); while (it.hasNext()) { it.next(); if (it.value().isNull()) { accountService->remove(it.key()); } else { accountService->setValue(it.key(), it.value()); } } syncIfDesired(); } /*! * \qmlmethod void AccountService::authenticate(jsobject sessionData) * * Perform the authentication on this account. The \a sessionData dictionary is * optional and if not given the value of \l {authData}{authData::parameters} will be used. * * Each call to this method will cause either of \l authenticated or * \l authenticationError signals to be emitted at some time later. Note that * the authentication might involve interactions with the network or with the * end-user, so don't expect these signals to be emitted immediately. * * \sa authenticated, authenticationError */ void AccountService::authenticate(const QVariantMap &sessionData) { DEBUG() << sessionData; if (Q_UNLIKELY(accountService == 0)) { QVariantMap error; error.insert("code", NoAccountError); error.insert("message", QLatin1String("Invalid AccountService")); Q_EMIT authenticationError(error); return; } Accounts::AuthData authData = accountService->authData(); if (identity == 0) { quint32 credentialsId = credentialsIdProperty.read().toUInt(); if (credentialsId == 0) credentialsId = authData.credentialsId(); identity = SignOn::Identity::existingIdentity(credentialsId, this); } if (authSession == 0) { authSession = identity->createSession(authData.method()); QObject::connect(authSession, SIGNAL(response(const SignOn::SessionData&)), this, SLOT(onAuthSessionResponse(const SignOn::SessionData&))); QObject::connect(authSession, SIGNAL(error(const SignOn::Error&)), this, SLOT(onAuthSessionError(const SignOn::Error&))); } QVariantMap allSessionData = mergeMaps(authData.parameters(), sessionData); authSession->process(allSessionData, authData.mechanism()); } /*! * \qmlmethod void AccountService::cancelAuthentication() * * Cancel an ongoing authentication on this account. This method does nothing * if there isn't any authentication process going on. * * \sa authenticate */ void AccountService::cancelAuthentication() { DEBUG(); if (authSession != 0) { authSession->cancel(); } } /*! * \qmlsignal AccountService::authenticated(jsobject reply) * * Emitted when the authentication has been successfully completed. The \a * reply object will contain the authentication data, which depends on the * authentication method used. */ /*! * \qmlsignal AccountService::authenticationError(jsobject error) * * Emitted when the authentication fails. The \a error object will contain the * following fields: * \list * \li \c code is a numeric error code (see Signon::Error for the meaning) * \li \c message is a textual description of the error, not meant for the end-user * \endlist */ void AccountService::classBegin() { } void AccountService::componentComplete() { constructed = true; } void AccountService::onAuthSessionResponse(const SignOn::SessionData &sessionData) { Q_EMIT authenticated(sessionData.toMap()); } void AccountService::onAuthSessionError(const SignOn::Error &error) { QVariantMap e; e.insert("code", errorCodeFromSignOn(error.type())); e.insert("message", error.message()); Q_EMIT authenticationError(e); } void AccountService::onCredentialsIdChanged() { if (accountService) { QVariant value = credentialsIdProperty.read(); accountService->setValue("CredentialsId", value); syncIfDesired(); } } ./src/manager.cpp0000644000004100000410000000423013071130602014140 0ustar www-datawww-data/* * Copyright (C) 2013 Canonical Ltd. * * Contact: Alberto Mardegan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "manager.h" #include "debug.h" #include #include using namespace OnlineAccounts; static QWeakPointer sharedManager; QSharedPointer SharedManager::instance() { QSharedPointer manager = sharedManager.toStrongRef(); if (manager.isNull()) { manager = QSharedPointer(new Accounts::Manager); sharedManager = manager; } return manager; } /*! * \qmltype Manager * \inqmlmodule Ubuntu.OnlineAccounts 0.1 * \ingroup Ubuntu * * \brief The account manager * * The Manager element is a singleton class which can be used to create new * online accounts or load existing ones. */ Manager::Manager(QObject *parent): QObject(parent), manager(SharedManager::instance()) { } Manager::~Manager() { } /*! * \qmlmethod object Manager::loadAccount(uint accountId) * * Loads the account identified by \a accountId. The returned object can be * used to instantiate an \l Account. * * \sa createAccount() */ QObject *Manager::loadAccount(uint accountId) { DEBUG() << accountId; return manager->account(accountId); } /*! * \qmlmethod object Manager::createAccount(string providerName) * * Create a new account interfacing to the provider identified by \a * providerName. * * \sa loadAccount() */ QObject *Manager::createAccount(const QString &providerName) { return manager->createAccount(providerName); } ./src/provider-model.cpp0000644000004100000410000001251013071130602015456 0ustar www-datawww-data/* * Copyright (C) 2013 Canonical Ltd. * * Contact: Alberto Mardegan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "debug.h" #include "provider-model.h" #include #include #include #include using namespace OnlineAccounts; /*! * \qmltype ProviderModel * \inqmlmodule Ubuntu.OnlineAccounts 0.1 * \ingroup Ubuntu * * \brief A model of the account providers * * The ProviderModel is a model representing the account providers installed on * the system. * * The model defines the following roles: * \list * \li \c displayName, the user-visible name of this provider * \li \c providerId, the unique identifier of the account provider * \li \c iconName, the name of the icon representing this provider * \li \c isSingleAccount, \a true if this provider supports creating one * account at most * \li \c translations, the localization domain for translating the provider's * display name * \endlist */ ProviderModel::ProviderModel(QObject *parent): QAbstractListModel(parent), manager(SharedManager::instance()), m_componentCompleted(false) { QObject::connect(this, SIGNAL(modelReset()), this, SIGNAL(countChanged())); } ProviderModel::~ProviderModel() { } /*! * \qmlproperty string ProviderModel::applicationId * If set, the model will only show those providers which are relevant for the * given \a applicationId. This means that a provider will only be shown if at * least one of its services can be used by the application, as described in * the application's manifest file. */ void ProviderModel::setApplicationId(const QString &applicationId) { if (m_applicationId == applicationId) return; m_applicationId = applicationId; if (m_componentCompleted) update(); Q_EMIT applicationIdChanged(); } QString ProviderModel::applicationId() const { return m_applicationId; } /*! * \qmlproperty int ProviderModel::count * The number of items in the model. */ int ProviderModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); return providers.count(); } /*! * \qmlmethod variant ProviderModel::get(int row, string roleName) * * Returns the data at \a row for the role \a roleName. */ QVariant ProviderModel::get(int row, const QString &roleName) const { int role = roleNames().key(roleName.toLatin1(), -1); return data(index(row), role); } QVariant ProviderModel::data(const QModelIndex &index, int role) const { if (index.row() < 0 || index.row() >= providers.count()) return QVariant(); const Accounts::Provider &provider = providers.at(index.row()); QVariant ret; switch (role) { case Qt::DisplayRole: ret = provider.displayName(); break; case ProviderIdRole: ret = provider.name(); break; case IconNameRole: ret = provider.iconName(); break; case IsSingleAccountRole: ret = provider.isSingleAccount(); break; case TranslationsRole: ret = provider.trCatalog(); break; } return ret; } QHash ProviderModel::roleNames() const { static QHash roles; if (roles.isEmpty()) { roles[Qt::DisplayRole] = "displayName"; roles[ProviderIdRole] = "providerId"; roles[IconNameRole] = "iconName"; roles[IsSingleAccountRole] = "isSingleAccount"; roles[TranslationsRole] = "translations"; } return roles; } void ProviderModel::classBegin() { } void ProviderModel::componentComplete() { update(); m_componentCompleted = true; } void ProviderModel::update() { beginResetModel(); Accounts::ProviderList allProviders = manager->providerList(); if (m_applicationId.isEmpty()) { providers = allProviders; } else { providers.clear(); /* This will be slightly simpler once * http://code.google.com/p/accounts-sso/issues/detail?id=214 is fixed. */ Accounts::Application application = manager->application(m_applicationId); Accounts::ServiceList supportedServices; Q_FOREACH(const Accounts::Service &service, manager->serviceList()) { if (!application.serviceUsage(service).isEmpty()) { supportedServices.append(service); } } Q_FOREACH(const Accounts::Provider &provider, allProviders) { bool hasSupportedServices = false; Q_FOREACH(const Accounts::Service &service, supportedServices) { if (service.provider() == provider.name()) { hasSupportedServices = true; break; } } if (hasSupportedServices) { providers.append(provider); } } } endResetModel(); } ./src/plugin.h0000644000004100000410000000206513071130602013475 0ustar www-datawww-data/* * Copyright (C) 2013 Canonical Ltd. * * Contact: Alberto Mardegan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef ONLINE_ACCOUNTS_PLUGIN_H #define ONLINE_ACCOUNTS_PLUGIN_H #include namespace OnlineAccounts { class Plugin: public QQmlExtensionPlugin { Q_OBJECT Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface") public: void registerTypes(const char *uri); }; }; // namespace #endif // ONLINE_ACCOUNTS_PLUGIN_H ./src/application.h0000644000004100000410000000244513071130602014504 0ustar www-datawww-data/* * Copyright (C) 2013 Canonical Ltd. * * Contact: Alberto Mardegan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef ONLINE_ACCOUNTS_APPLICATION_H #define ONLINE_ACCOUNTS_APPLICATION_H #include #include namespace OnlineAccounts { class Application: public QObject, public Accounts::Application { Q_OBJECT Q_PROPERTY(QString applicationId READ name CONSTANT) Q_PROPERTY(QString description READ description CONSTANT) public: Application(const Accounts::Application &application, QObject *parent = 0); ~Application(); Q_INVOKABLE QString serviceUsage(const Accounts::Service &service); }; }; // namespace #endif // ONLINE_ACCOUNTS_APPLICATION_H ./src/credentials.h0000644000004100000410000000554513071130602014502 0ustar www-datawww-data/* * Copyright (C) 2013 Canonical Ltd. * * Contact: Alberto Mardegan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef ONLINE_ACCOUNTS_CREDENTIALS_H #define ONLINE_ACCOUNTS_CREDENTIALS_H #include #include #include #include #include namespace OnlineAccounts { class Credentials: public QObject { Q_OBJECT Q_PROPERTY(quint32 credentialsId READ credentialsId WRITE setCredentialsId \ NOTIFY credentialsIdChanged) Q_PROPERTY(QString caption READ caption WRITE setCaption \ NOTIFY captionChanged) Q_PROPERTY(QString userName READ userName WRITE setUserName \ NOTIFY userNameChanged) Q_PROPERTY(QString secret READ secret WRITE setSecret NOTIFY secretChanged) Q_PROPERTY(bool storeSecret READ storeSecret WRITE setStoreSecret \ NOTIFY storeSecretChanged) Q_PROPERTY(QStringList acl READ acl WRITE setAcl NOTIFY aclChanged) Q_PROPERTY(QVariantMap methods READ methods WRITE setMethods NOTIFY methodsChanged) public: Credentials(QObject *parent = 0); ~Credentials(); void setCredentialsId(quint32 credentialsId); quint32 credentialsId() const; void setCaption(const QString &caption); QString caption() const; void setUserName(const QString &userName); QString userName() const; void setSecret(const QString &secret); QString secret() const; void setStoreSecret(bool storeSecret); bool storeSecret() const; void setAcl(const QStringList &acl); QStringList acl() const; void setMethods(const QVariantMap &methods); QVariantMap methods() const; Q_INVOKABLE void sync(); Q_INVOKABLE void remove(); Q_SIGNALS: void credentialsIdChanged(); void captionChanged(); void userNameChanged(); void secretChanged(); void storeSecretChanged(); void aclChanged(); void methodsChanged(); void synced(); void removed(); private: void ensureIdentity(); void setupIdentity(); private Q_SLOTS: void onInfo(const SignOn::IdentityInfo &info); void onStored(const quint32 id); private: quint32 m_credentialsId; SignOn::Identity *identity; SignOn::IdentityInfo info; }; }; // namespace #endif // ONLINE_ACCOUNTS_CREDENTIALS_H ./src/account-service.h0000644000004100000410000001003413071130602015264 0ustar www-datawww-data/* * Copyright (C) 2013 Canonical Ltd. * * Contact: Alberto Mardegan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef ONLINE_ACCOUNTS_ACCOUNT_SERVICE_H #define ONLINE_ACCOUNTS_ACCOUNT_SERVICE_H #include #include #include #include #include namespace Accounts { class AccountService; }; namespace SignOn { class AuthSession; class Error; class Identity; class SessionData; }; namespace OnlineAccounts { class AccountService: public QObject, public QQmlParserStatus { Q_OBJECT Q_INTERFACES(QQmlParserStatus) Q_ENUMS(ErrorCode) Q_PROPERTY(QObject *objectHandle READ objectHandle \ WRITE setObjectHandle NOTIFY objectHandleChanged) Q_PROPERTY(bool enabled READ enabled NOTIFY enabledChanged) Q_PROPERTY(bool serviceEnabled READ serviceEnabled NOTIFY settingsChanged) Q_PROPERTY(QVariantMap provider READ provider NOTIFY objectHandleChanged) Q_PROPERTY(QVariantMap service READ service NOTIFY objectHandleChanged) Q_PROPERTY(QString displayName READ displayName NOTIFY displayNameChanged) Q_PROPERTY(uint accountId READ accountId NOTIFY objectHandleChanged) Q_PROPERTY(QVariantMap settings READ settings NOTIFY settingsChanged) Q_PROPERTY(QVariantMap authData READ authData NOTIFY settingsChanged) Q_PROPERTY(bool autoSync READ autoSync WRITE setAutoSync \ NOTIFY autoSyncChanged) Q_PROPERTY(QObject *credentials READ credentials WRITE setCredentials \ NOTIFY credentialsChanged) public: enum ErrorCode { NoError = 0, NoAccountError, UserCanceledError, PermissionDeniedError, NetworkError, SslError, InteractionRequiredError, }; AccountService(QObject *parent = 0); ~AccountService(); void setObjectHandle(QObject *object); QObject *objectHandle() const; bool enabled() const; bool serviceEnabled() const; QVariantMap provider() const; QVariantMap service() const; QString displayName() const; uint accountId() const; QVariantMap settings() const; QVariantMap authData() const; void setAutoSync(bool autoSync); bool autoSync() const; void setCredentials(QObject *credentials); QObject *credentials() const; Q_INVOKABLE void authenticate(const QVariantMap &sessionData = QVariantMap()); Q_INVOKABLE void cancelAuthentication(); Q_INVOKABLE void updateServiceEnabled(bool enabled); Q_INVOKABLE void updateSettings(const QVariantMap &settings); // reimplemented virtual methods void classBegin(); void componentComplete(); Q_SIGNALS: void objectHandleChanged(); void enabledChanged(); void displayNameChanged(); void settingsChanged(); void autoSyncChanged(); void credentialsChanged(); void authenticated(const QVariantMap &reply); void authenticationError(const QVariantMap &error); private Q_SLOTS: void onAuthSessionResponse(const SignOn::SessionData &sessionData); void onAuthSessionError(const SignOn::Error &error); void onCredentialsIdChanged(); private: void syncIfDesired(); private: QPointer accountService; SignOn::Identity *identity; QPointer authSession; QPointer m_credentials; QQmlProperty credentialsIdProperty; bool constructed; bool m_autoSync; }; }; // namespace #endif // ONLINE_ACCOUNTS_ACCOUNT_SERVICE_H ./src/account.cpp0000644000004100000410000001720013071130602014163 0ustar www-datawww-data/* * Copyright (C) 2013 Canonical Ltd. * * Contact: Alberto Mardegan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "account.h" #include "debug.h" #include #include #include #include using namespace OnlineAccounts; /*! * \qmltype Account * \inqmlmodule Ubuntu.OnlineAccounts 0.1 * \ingroup Ubuntu * * \brief Represents an instance of an online account. * * The Account element represents an online account. It is functional only if * its \a objectHandle property is set to a valid account, which can be * obtained with Manager.loadAccount() or Manager.createAccount(). */ Account::Account(QObject *parent): QObject(parent), account(0), accountService(0) { } Account::~Account() { } /*! * \qmlproperty object Account::objectHandle * An opaque handle to the underlying C++ object. Until the property is set, * the Account element is uninitialized. Similarly, if the C++ object is * destroyed, expect the Account to become invalid. */ void Account::setObjectHandle(QObject *object) { DEBUG() << object; Accounts::Account *a = qobject_cast(object); if (Q_UNLIKELY(a == 0)) return; if (a == account) return; account = a; QObject::connect(account, SIGNAL(displayNameChanged(const QString &)), this, SIGNAL(displayNameChanged())); QObject::connect(account, SIGNAL(synced()), this, SIGNAL(synced())); QObject::connect(account, SIGNAL(removed()), this, SLOT(onRemoved())); /* Setup an AccountService object to monitor the settings of the global * account. */ delete accountService; accountService = new Accounts::AccountService(account, Accounts::Service(), account); QObject::connect(accountService, SIGNAL(enabled(bool)), this, SIGNAL(enabledChanged())); Q_EMIT objectHandleChanged(); /* Emit the changed signals for all other properties, to make sure * that all bindings are updated. */ Q_EMIT accountIdChanged(); Q_EMIT enabledChanged(); Q_EMIT displayNameChanged(); } QObject *Account::objectHandle() const { return account; } /*! * \qmlproperty bool Account::enabled * This read-only property tells whether the Account is enabled. An * application shouldn't use an Account which is disabled. */ bool Account::enabled() const { if (Q_UNLIKELY(accountService == 0)) return false; return accountService->enabled(); } /*! * \qmlproperty jsobject Account::provider * An immutable object representing the provider which provides the account. * The returned object will have at least these members: * \list * \li \c id is the unique identified for this provider * \li \c displayName * \li \c iconName * \endlist */ QVariantMap Account::provider() const { QVariantMap map; if (Q_UNLIKELY(account == 0)) return map; Accounts::Provider provider = account->provider(); map.insert("id", provider.name()); map.insert("displayName", provider.displayName()); map.insert("iconName", provider.iconName()); return map; } /*! * \qmlproperty string Account::displayName * The account's display name (usually the user's login or ID). */ QString Account::displayName() const { if (Q_UNLIKELY(account == 0)) return QString(); return account->displayName(); } /*! * \qmlproperty string Account::accountId * The account's numeric ID. This is 0 until the account has been stored into the DB. */ uint Account::accountId() const { if (Q_UNLIKELY(account == 0)) return 0; return account->id(); } /*! * \qmlproperty object Account::accountServiceHandle * A C++ object which can be used to instantiate an AccountService by setting * it as the value for the \l AccountService::objectHandle property. */ QObject *Account::accountServiceHandle() const { return accountService; } /*! * \qmlmethod void Account::updateDisplayName(string displayName) * * Changes the display name of the account. * * \sa sync() */ void Account::updateDisplayName(const QString &displayName) { if (Q_UNLIKELY(account == 0)) return; account->setDisplayName(displayName); } /*! * \qmlmethod void Account::updateEnabled(bool enabled) * * Enables or disables the account. * * \sa sync() */ void Account::updateEnabled(bool enabled) { if (Q_UNLIKELY(account == 0)) return; account->selectService(); account->setEnabled(enabled); } /*! * \qmlmethod void Account::sync() * * Writes the changes to the permanent account storage. */ void Account::sync() { if (Q_UNLIKELY(account == 0)) return; account->sync(); } /*! * \qmlmethod void Account::remove() * * Deletes the account from the permanent storage. This method accepts an * optional parameter, which tells whether the credentials associated with * the account should also be removed: * \list * \li \c Account.RemoveAccountOnly * \li \c Account.RemoveCredentials - the default * \endlist */ void Account::remove(RemovalOptions options) { if (Q_UNLIKELY(account == 0)) return; if (options & RemoveCredentials) { /* Get all the IDs of the credentials used by this account */ QList credentialIds; account->selectService(); uint credentialsId = account->value("CredentialsId").toUInt(); if (credentialsId != 0) credentialIds.append(credentialsId); Q_FOREACH (const Accounts::Service &service, account->services()) { account->selectService(service); credentialsId = account->value("CredentialsId").toUInt(); if (credentialsId != 0) credentialIds.append(credentialsId); } /* Instantiate an Identity object for each of them */ Q_FOREACH (uint credentialsId, credentialIds) { SignOn::Identity *identity = SignOn::Identity::existingIdentity(credentialsId, this); QObject::connect(identity, SIGNAL(removed()), this, SLOT(onIdentityRemoved())); /* Since we don't do any error handling in case the removal of the * identity failed, we connect to the same slot. */ QObject::connect(identity, SIGNAL(error(const SignOn::Error&)), this, SLOT(onIdentityRemoved())); identities.append(identity); } } account->remove(); account->sync(); } /*! * \qmlsignal Account::synced() * * Emitted when the account changes have been stored into the permanent storage. */ void Account::onRemoved() { Q_FOREACH (SignOn::Identity *identity, identities) { /* Remove the associated credentials */ identity->remove(); } /* Don't emit the removed() signal until all associated Identity objects * have been removed */ if (identities.isEmpty()) { Q_EMIT removed(); } } void Account::onIdentityRemoved() { SignOn::Identity *identity = qobject_cast(sender()); identities.removeAll(identity); identity->deleteLater(); if (identities.isEmpty()) { Q_EMIT removed(); } } ./src/qmldir.in0000644000004100000410000000007613071130602013646 0ustar www-datawww-datamodule $${API_URI} plugin $${TARGET} typeinfo plugin.qmltypes ./src/credentials.cpp0000644000004100000410000001716413071130602015035 0ustar www-datawww-data/* * Copyright (C) 2013 Canonical Ltd. * * Contact: Alberto Mardegan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "credentials.h" #include "debug.h" using namespace OnlineAccounts; /*! * \qmltype Credentials * \inqmlmodule Ubuntu.OnlineAccounts 0.1 * \ingroup Ubuntu * * \brief Holds the account's credentials * * The Credentials element contains the information about an account's * credentials. Informations like user name and password are stored into the * account's secret storage via this object. * If the \l credentialsId property is set to a valid credentials ID (which can * be obtained via the AccountService's \l * {AccountService::authData}{authData.credentialsId} property) the Credentials * element will load the informations stored in the secrets database, with the * notable exception of the \l secret field, which cannot be read back via this * interface (but only via the \l AccountService::authenticate method); if the * \l credentialsId field is not set, then this interface can be used to create * a new record in the secrets storage, by calling the \l sync() method once * all the desired fields have been set. */ Credentials::Credentials(QObject *parent): QObject(parent), m_credentialsId(0), identity(0) { } Credentials::~Credentials() { } /*! * \qmlproperty quint32 Credentials::credentialsId * Numeric identifier of the credentials record in the secret storage database. * A value of \a 0 means that this object has not been stored into the database * yet. * \sa sync */ void Credentials::setCredentialsId(quint32 credentialsId) { if (credentialsId == m_credentialsId) return; delete identity; if (credentialsId != 0) { identity = SignOn::Identity::existingIdentity(credentialsId, this); if (identity != 0) { setupIdentity(); identity->queryInfo(); } } else { identity = 0; /* We'll instantiate it if/when needed */ } m_credentialsId = credentialsId; Q_EMIT credentialsIdChanged(); } quint32 Credentials::credentialsId() const { return m_credentialsId; } /*! * \qmlproperty string Credentials::caption * A description of the credentials. This could be set to the name of the * account provider, for instance. */ void Credentials::setCaption(const QString &caption) { if (caption == info.caption()) return; info.setCaption(caption); Q_EMIT captionChanged(); } QString Credentials::caption() const { return info.caption(); } /*! * \qmlproperty string Credentials::userName * The user name. */ void Credentials::setUserName(const QString &userName) { if (userName == info.userName()) return; info.setUserName(userName); Q_EMIT userNameChanged(); } QString Credentials::userName() const { return info.userName(); } /*! * \qmlproperty string Credentials::secret * The secret information for this credentials; usually this is the user's password. * Note that when retrieving a Credentials object from the secrets database, * this field will not be retrieved. See the detailed description of the * Credentials element for a full explanation of this. * * \sa credentialsId */ void Credentials::setSecret(const QString &secret) { info.setSecret(secret); Q_EMIT secretChanged(); } QString Credentials::secret() const { return info.secret(); } /*! * \qmlproperty bool Credentials::storeSecret * Whether the secret should be stored in the secrets storage. */ void Credentials::setStoreSecret(bool storeSecret) { if (storeSecret == info.isStoringSecret()) return; info.setStoreSecret(storeSecret); Q_EMIT storeSecretChanged(); } bool Credentials::storeSecret() const { return info.isStoringSecret(); } /*! * \qmlproperty list Credentials::acl * The ACL (Access Control List) for the credentials. The string \a "*" should * be used when no access control needs to be performed. */ void Credentials::setAcl(const QStringList &acl) { info.setAccessControlList(acl); Q_EMIT aclChanged(); } QStringList Credentials::acl() const { return info.accessControlList(); } /*! * \qmlproperty jsobject Credentials::methods * A dictionary describing the authentication methods and mechanisms which are * allowed on the credentials. The keys of the dictionary should be the * authentication methods, and the values should be lists of mechanisms. * \qml * Credentials { * methods: { "oauth2": [ "web_server", "user_agent"], "password": [ "password" ] } * } * \endqml */ void Credentials::setMethods(const QVariantMap &methods) { /* To keep things simple, always delete all existing methods, and then add * the new ones. */ Q_FOREACH (const QString &method, info.methods()) { info.removeMethod(method); } QMapIterator it(methods); while (it.hasNext()) { it.next(); info.setMethod(it.key(), it.value().toStringList()); } } QVariantMap Credentials::methods() const { QVariantMap methods; Q_FOREACH (const QString &method, info.methods()) { QStringList mechanisms = info.mechanisms(method); methods.insert(method, mechanisms); } return methods; } /*! * \qmlmethod void Credentials::sync() * * Writes the changes to the secrets storage. * * \sa synced */ void Credentials::sync() { ensureIdentity(); identity->storeCredentials(info); } /*! * \qmlmethod void Credentials::remove() * * Deletes the credentials from the secrets storage. * * \sa removed */ void Credentials::remove() { /* If we don't have an identity object, this means that this object was * never stored; we have nothing to do in this case. */ if (Q_UNLIKELY(identity == 0)) return; identity->remove(); } /*! * \qmlsignal Credentials::synced() * * Emitted when the changes have been stored into the permanent secrets storage. */ /*! * \qmlsignal Credentials::removed() * * Emitted when the credentials have been deleted from the secrets storage. */ void Credentials::ensureIdentity() { if (identity == 0) { identity = SignOn::Identity::newIdentity(info, this); setupIdentity(); } } void Credentials::setupIdentity() { QObject::connect(identity, SIGNAL(info(const SignOn::IdentityInfo&)), this, SLOT(onInfo(const SignOn::IdentityInfo&))); QObject::connect(identity, SIGNAL(credentialsStored(const quint32)), this, SLOT(onStored(const quint32))); QObject::connect(identity, SIGNAL(removed()), this, SIGNAL(removed())); } void Credentials::onInfo(const SignOn::IdentityInfo &info) { this->info = info; /* Emit the notification signals for all the properties; if this turns out * to be an issue, we could just emit the signals for those properties * whose value actually changed. */ Q_EMIT credentialsIdChanged(); Q_EMIT captionChanged(); Q_EMIT userNameChanged(); Q_EMIT secretChanged(); Q_EMIT storeSecretChanged(); Q_EMIT aclChanged(); Q_EMIT methodsChanged(); Q_EMIT synced(); } void Credentials::onStored(const quint32 id) { m_credentialsId = id; identity->queryInfo(); } ./src/application.cpp0000644000004100000410000000347613071130602015044 0ustar www-datawww-data/* * Copyright (C) 2013 Canonical Ltd. * * Contact: Alberto Mardegan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "application.h" #include "debug.h" using namespace OnlineAccounts; /*! * \qmltype Application * \inqmlmodule Ubuntu.OnlineAccounts 0.1 * \ingroup Ubuntu * * \brief Represents a client application of Online Accounts. * * The Application element represents an application using online accounts. * Currently, instances of this object cannot be created directly, but are * instantiated by the \l ApplicationModel element. */ Application::Application(const Accounts::Application &application, QObject *parent): QObject(parent), Accounts::Application(application) { } Application::~Application() { } /*! * \qmlproperty string Application::applicationId * Unique identifier for this application. */ /*! * \qmlproperty string Application::description * Description of the application. */ /*! * \qmlmethod string Application::serviceUsage(Service service) * * Returns a textual description of how the application can make use of \a * service. */ QString Application::serviceUsage(const Accounts::Service &service) { return Accounts::Application::serviceUsage(service); } ./src/manager.h0000644000004100000410000000247213071130602013613 0ustar www-datawww-data/* * Copyright (C) 2013 Canonical Ltd. * * Contact: Alberto Mardegan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef ONLINE_ACCOUNTS_MANAGER_H #define ONLINE_ACCOUNTS_MANAGER_H #include #include namespace Accounts { class Manager; }; namespace OnlineAccounts { class SharedManager { public: static QSharedPointer instance(); }; class Manager: public QObject { Q_OBJECT public: Manager(QObject *parent = 0); ~Manager(); Q_INVOKABLE QObject *loadAccount(uint accountId); Q_INVOKABLE QObject *createAccount(const QString &providerName); private: QSharedPointer manager; }; }; // namespace #endif // ONLINE_ACCOUNTS_MANAGER_H ./tests/0000755000004100000410000000000013071130602012376 5ustar www-datawww-data./tests/tst_plugin.cpp0000644000004100000410000017505313071130602015305 0ustar www-datawww-data/* * Copyright (C) 2013 Canonical Ltd. * * Contact: Alberto Mardegan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include using namespace Accounts; static bool mapIsSubset(const QVariantMap &set, const QVariantMap &test) { QMapIterator it(set); while (it.hasNext()) { it.next(); if (QMetaType::Type(it.value().type()) == QMetaType::QVariantMap) { if (!mapIsSubset(it.value().toMap(), test.value(it.key()).toMap())) { return false; } } else if (test.value(it.key()) != it.value()) { qDebug() << "Maps differ: expected" << it.value() << "but found" << test.value(it.key()); return false; } } return true; } class PluginTest: public QObject { Q_OBJECT public: PluginTest(); private Q_SLOTS: void initTestCase(); void testLoadPlugin(); void testEmptyModel(); void testModel(); void testModelSignals(); void testModelDisplayName(); void testProviderModel(); void testProviderModelWithApplication(); void testAccountService(); void testAccountServiceUpdate(); void testAuthentication_data(); void testAuthentication(); void testAuthenticationErrors_data(); void testAuthenticationErrors(); void testAuthenticationDeleted(); void testAuthenticationCancel(); void testAuthenticationWithCredentials(); void testManagerCreate(); void testManagerLoad(); void testAccountInvalid(); void testAccount(); void testCredentials(); void testAccountCredentialsRemoval_data(); void testAccountCredentialsRemoval(); void testAccountServiceCredentials(); void testApplicationModel(); private: void clearDb(); QVariant get(const QAbstractListModel *model, int row, QString roleName); }; PluginTest::PluginTest(): QObject(0) { } void PluginTest::clearDb() { QDir dbroot(QString::fromLatin1(qgetenv("ACCOUNTS"))); dbroot.remove("accounts.db"); } QVariant PluginTest::get(const QAbstractListModel *model, int row, QString roleName) { QHash roleNames = model->roleNames(); int role = roleNames.key(roleName.toLatin1(), -1); return model->data(model->index(row), role); } void PluginTest::initTestCase() { qputenv("QML2_IMPORT_PATH", "../src"); qputenv("ACCOUNTS", "/tmp/"); qputenv("AG_APPLICATIONS", TEST_DATA_DIR); qputenv("AG_SERVICES", TEST_DATA_DIR); qputenv("AG_SERVICE_TYPES", TEST_DATA_DIR); qputenv("AG_PROVIDERS", TEST_DATA_DIR); qputenv("XDG_DATA_HOME", TEST_DATA_DIR); clearDb(); } void PluginTest::testLoadPlugin() { QQmlEngine engine; QQmlComponent component(&engine); component.setData("import Ubuntu.OnlineAccounts 0.1\n" "AccountServiceModel {}", QUrl()); QObject *object = component.create(); QVERIFY(object != 0); delete object; } void PluginTest::testEmptyModel() { clearDb(); QQmlEngine engine; QQmlComponent component(&engine); component.setData("import Ubuntu.OnlineAccounts 0.1\n" "AccountServiceModel {}", QUrl()); QObject *object = component.create(); QVERIFY(object != 0); QAbstractListModel *model = qobject_cast(object); QVERIFY(model != 0); QCOMPARE(model->rowCount(), 0); /* We'll now add some accounts but set the service type filter so that they * should not appear in the model. */ model->setProperty("serviceType", QString("e-mail")); QCOMPARE(model->property("serviceType").toString(), QString("e-mail")); /* Create some disabled accounts */ Manager *manager = new Manager(this); Service coolMail = manager->service("coolmail"); Service badMail = manager->service("badmail"); Account *account1 = manager->createAccount("cool"); QVERIFY(account1 != 0); account1->setEnabled(false); account1->setDisplayName("CoolAccount"); account1->selectService(coolMail); account1->setEnabled(true); account1->syncAndBlock(); Account *account2 = manager->createAccount("bad"); QVERIFY(account2 != 0); account2->setEnabled(true); account2->setDisplayName("BadAccount"); account2->selectService(badMail); account2->setEnabled(false); account2->syncAndBlock(); QTest::qWait(10); QCOMPARE(model->rowCount(), 0); delete manager; delete object; } void PluginTest::testModel() { clearDb(); /* Create some accounts */ Manager *manager = new Manager(this); Service coolMail = manager->service("coolmail"); Service coolShare = manager->service("coolshare"); Service badMail = manager->service("badmail"); Service badShare = manager->service("badshare"); Account *account1 = manager->createAccount("cool"); QVERIFY(account1 != 0); account1->setEnabled(true); account1->setDisplayName("CoolAccount"); account1->selectService(coolMail); account1->setEnabled(true); account1->selectService(coolShare); account1->setEnabled(false); account1->syncAndBlock(); Account *account2 = manager->createAccount("bad"); QVERIFY(account2 != 0); account2->setEnabled(true); account2->setDisplayName("BadAccount"); account2->selectService(badMail); account2->setEnabled(true); account2->selectService(badShare); account2->setEnabled(true); account2->syncAndBlock(); QQmlEngine engine; QQmlComponent component(&engine); component.setData("import Ubuntu.OnlineAccounts 0.1\n" "AccountServiceModel {}", QUrl()); QObject *object = component.create(); QVERIFY(object != 0); QAbstractListModel *model = qobject_cast(object); QVERIFY(model != 0); QCOMPARE(model->rowCount(), 3); QCOMPARE(model->property("count").toInt(), 3); QCOMPARE(get(model, 0, "displayName").toString(), QString("BadAccount")); QCOMPARE(get(model, 0, "providerName").toString(), QString("Bad provider")); QCOMPARE(get(model, 0, "accountId").toUInt(), account2->id()); QObject *accountHandle = get(model, 0, "accountHandle").value(); Account *tmpAccount = qobject_cast(accountHandle); QVERIFY(tmpAccount != 0); QCOMPARE(tmpAccount->id(), account2->id()); // Same value, but using the deprecated role name QCOMPARE(get(model, 0, "account").value(), accountHandle); QCOMPARE(get(model, 1, "displayName").toString(), QString("BadAccount")); QCOMPARE(get(model, 1, "providerName").toString(), QString("Bad provider")); QCOMPARE(get(model, 2, "displayName").toString(), QString("CoolAccount")); QCOMPARE(get(model, 2, "providerName").toString(), QString("Cool provider")); QCOMPARE(get(model, 2, "accountId").toUInt(), account1->id()); QVariant value; QVERIFY(QMetaObject::invokeMethod(model, "get", Q_RETURN_ARG(QVariant, value), Q_ARG(int, 2), Q_ARG(QString, "providerName"))); QCOMPARE(value.toString(), QString("Cool provider")); QObject *accountServiceHandle = get(model, 2, "accountServiceHandle").value(); QVERIFY(accountServiceHandle != 0); QCOMPARE(accountServiceHandle->metaObject()->className(), "Accounts::AccountService"); // Same value, but using the deprecated role name QCOMPARE(get(model, 2, "accountService").value(), accountServiceHandle); model->setProperty("includeDisabled", true); QCOMPARE(model->property("includeDisabled").toBool(), true); QTest::qWait(10); QCOMPARE(model->rowCount(), 4); QCOMPARE(get(model, 0, "enabled").toBool(), true); QCOMPARE(get(model, 1, "enabled").toBool(), true); QCOMPARE(get(model, 2, "enabled").toBool(), true); QCOMPARE(get(model, 3, "enabled").toBool(), false); /* Test the accountId filter */ model->setProperty("accountId", account1->id()); QCOMPARE(model->property("accountId").toUInt(), account1->id()); QTest::qWait(10); QCOMPARE(model->rowCount(), 2); QCOMPARE(get(model, 0, "accountId").toUInt(), account1->id()); QCOMPARE(get(model, 1, "accountId").toUInt(), account1->id()); model->setProperty("accountId", 0); /* Test the account filter */ model->setProperty("account", QVariant::fromValue(account2)); QCOMPARE(model->property("account").value(), account2); QTest::qWait(10); QCOMPARE(model->rowCount(), 2); QCOMPARE(get(model, 0, "accountId").toUInt(), account2->id()); QCOMPARE(get(model, 1, "accountId").toUInt(), account2->id()); model->setProperty("account", QVariant::fromValue(account2)); QCOMPARE(model->property("account").value(), account2); model->setProperty("account", QVariant::fromValue(0)); /* Test the application filter */ model->setProperty("applicationId", QString("mailer")); QCOMPARE(model->property("applicationId").toString(), QString("mailer")); QTest::qWait(10); QCOMPARE(model->rowCount(), 2); QSet services; services.insert(get(model, 0, "serviceName").toString()); services.insert(get(model, 1, "serviceName").toString()); QSet expectedServices; expectedServices.insert("Cool Mail"); expectedServices.insert("Bad Mail"); QCOMPARE(services, expectedServices); /* Reset the application filter */ model->setProperty("applicationId", QString()); /* Test the provider filter */ model->setProperty("provider", QString("bad")); QCOMPARE(model->property("provider").toString(), QString("bad")); QTest::qWait(10); QCOMPARE(model->rowCount(), 2); QCOMPARE(get(model, 0, "providerName").toString(), QString("Bad provider")); QCOMPARE(get(model, 1, "providerName").toString(), QString("Bad provider")); /* Test the service filter */ model->setProperty("service", QString("coolmail")); QCOMPARE(model->property("service").toString(), QString("coolmail")); QTest::qWait(10); QCOMPARE(model->rowCount(), 0); /* Reset the provider, to get some results */ model->setProperty("provider", QString()); QTest::qWait(10); QCOMPARE(model->rowCount(), 1); QCOMPARE(get(model, 0, "providerName").toString(), QString("Cool provider")); QCOMPARE(get(model, 0, "serviceName").toString(), QString("Cool Mail")); QCOMPARE(get(model, 0, "enabled").toBool(), true); /* Retrieve global accounts */ model->setProperty("service", QString("global")); QCOMPARE(model->property("service").toString(), QString("global")); QTest::qWait(10); QCOMPARE(model->rowCount(), 2); QCOMPARE(get(model, 0, "providerName").toString(), QString("Bad provider")); QCOMPARE(get(model, 1, "providerName").toString(), QString("Cool provider")); /* The AccountService objects should refer to a null Service */ for (int i = 0; i < 2; i++) { QObject *tmp = get(model, i, "accountServiceHandle").value(); AccountService *accountService1 = qobject_cast(tmp); QVERIFY(accountService1 != 0); QVERIFY(!accountService1->service().isValid()); } /* Test the service-type filter */ model->setProperty("serviceType", QString("sharing")); QCOMPARE(model->property("serviceType").toString(), QString("sharing")); /* Reset the service, to get some results */ model->setProperty("service", QString()); QTest::qWait(10); QCOMPARE(model->rowCount(), 2); QCOMPARE(get(model, 0, "serviceName").toString(), QString("Bad Share")); QCOMPARE(get(model, 1, "serviceName").toString(), QString("Cool Share")); delete manager; delete object; } void PluginTest::testModelSignals() { clearDb(); /* Create one account */ Manager *manager = new Manager(this); Service coolMail = manager->service("coolmail"); Service coolShare = manager->service("coolshare"); Service badMail = manager->service("badmail"); Service badShare = manager->service("badshare"); Account *account1 = manager->createAccount("cool"); QVERIFY(account1 != 0); account1->setEnabled(true); account1->setDisplayName("CoolAccount"); account1->selectService(coolMail); account1->setEnabled(true); account1->selectService(coolShare); account1->setEnabled(false); account1->syncAndBlock(); QQmlEngine engine; QQmlComponent component(&engine); component.setData("import Ubuntu.OnlineAccounts 0.1\n" "AccountServiceModel {}", QUrl()); QObject *object = component.create(); QVERIFY(object != 0); QAbstractListModel *model = qobject_cast(object); QVERIFY(model != 0); QCOMPARE(model->rowCount(), 1); QCOMPARE(model->property("count").toInt(), 1); QCOMPARE(get(model, 0, "displayName").toString(), QString("CoolAccount")); QCOMPARE(get(model, 0, "providerName").toString(), QString("Cool provider")); QCOMPARE(get(model, 0, "serviceName").toString(), QString("Cool Mail")); /* Enable the cool share service, and verify that it appears in the model */ QSignalSpy countChanged(model, SIGNAL(countChanged())); QSignalSpy rowsInserted(model, SIGNAL(rowsInserted(const QModelIndex&,int,int))); account1->selectService(coolShare); account1->setEnabled(true); account1->syncAndBlock(); QTest::qWait(50); QCOMPARE(model->rowCount(), 2); QCOMPARE(rowsInserted.count(), 1); QCOMPARE(countChanged.count(), 1); rowsInserted.clear(); /* Disable the cool mail service, and verify that it gets removed */ QSignalSpy rowsRemoved(model, SIGNAL(rowsRemoved(const QModelIndex&,int,int))); account1->selectService(coolMail); account1->setEnabled(false); account1->syncAndBlock(); QTest::qWait(50); QCOMPARE(model->rowCount(), 1); QCOMPARE(rowsInserted.count(), 0); QCOMPARE(rowsRemoved.count(), 1); rowsRemoved.clear(); /* Create a second account */ Account *account2 = manager->createAccount("bad"); QVERIFY(account2 != 0); account2->setEnabled(false); account2->setDisplayName("BadAccount"); account2->selectService(badMail); account2->setEnabled(true); account2->selectService(badShare); account2->setEnabled(true); account2->syncAndBlock(); QTest::qWait(50); /* It's disabled, so nothing should have changed */ QCOMPARE(model->rowCount(), 1); QCOMPARE(rowsInserted.count(), 0); QCOMPARE(rowsRemoved.count(), 0); /* Enable it */ account2->selectService(); account2->setEnabled(true); account2->syncAndBlock(); QTest::qWait(50); QCOMPARE(model->rowCount(), 3); QCOMPARE(rowsInserted.count(), 2); QCOMPARE(rowsRemoved.count(), 0); rowsInserted.clear(); /* Include disabled */ model->setProperty("includeDisabled", true); QTest::qWait(50); QCOMPARE(model->rowCount(), 4); /* The model is being reset: all rows are deleted and then re-added */ QCOMPARE(rowsInserted.count(), 1); QCOMPARE(rowsRemoved.count(), 1); rowsInserted.clear(); rowsRemoved.clear(); QCOMPARE(get(model, 0, "enabled").toBool(), true); QCOMPARE(get(model, 1, "enabled").toBool(), true); QCOMPARE(get(model, 2, "enabled").toBool(), false); QCOMPARE(get(model, 3, "enabled").toBool(), true); /* Enable cool mail, and check for the dataChanged signal */ QSignalSpy dataChanged(model, SIGNAL(dataChanged(const QModelIndex&,const QModelIndex&))); account1->selectService(coolMail); account1->setEnabled(true); account1->syncAndBlock(); QTest::qWait(50); QCOMPARE(dataChanged.count(), 1); QModelIndex index = qvariant_cast(dataChanged.at(0).at(0)); QCOMPARE(index.row(), 2); QCOMPARE(rowsInserted.count(), 0); QCOMPARE(rowsRemoved.count(), 0); dataChanged.clear(); QCOMPARE(get(model, 2, "enabled").toBool(), true); /* Delete the first account */ account1->remove(); account1->syncAndBlock(); QTest::qWait(50); QCOMPARE(model->rowCount(), 2); QCOMPARE(rowsInserted.count(), 0); /* We expect one single signal carrying two rows */ QCOMPARE(rowsRemoved.count(), 1); QCOMPARE(rowsRemoved.at(0).at(1).toInt(), 2); QCOMPARE(rowsRemoved.at(0).at(2).toInt(), 3); rowsRemoved.clear(); /* Create a third account */ Account *account3 = manager->createAccount("bad"); QVERIFY(account3 != 0); account3->setEnabled(true); account3->setDisplayName("Second BadAccount"); account3->selectService(badMail); account3->setEnabled(true); account3->selectService(badShare); account3->setEnabled(false); account3->syncAndBlock(); QTest::qWait(50); QCOMPARE(model->rowCount(), 4); /* We expect one single signal carrying two rows */ QCOMPARE(rowsInserted.count(), 1); QCOMPARE(rowsInserted.at(0).at(1).toInt(), 2); QCOMPARE(rowsInserted.at(0).at(2).toInt(), 3); QCOMPARE(rowsRemoved.count(), 0); rowsInserted.clear(); delete manager; delete object; } void PluginTest::testModelDisplayName() { clearDb(); /* Create one account */ Manager *manager = new Manager(this); Service coolMail = manager->service("coolmail"); Service coolShare = manager->service("coolshare"); Service badMail = manager->service("badmail"); Service badShare = manager->service("badshare"); Account *account1 = manager->createAccount("cool"); QVERIFY(account1 != 0); account1->setEnabled(true); account1->setDisplayName("CoolAccount"); account1->selectService(coolMail); account1->setEnabled(true); account1->selectService(coolShare); account1->setEnabled(false); account1->syncAndBlock(); QQmlEngine engine; QQmlComponent component(&engine); component.setData("import Ubuntu.OnlineAccounts 0.1\n" "AccountServiceModel {\n" " includeDisabled: true\n" "}", QUrl()); QObject *object = component.create(); QVERIFY(object != 0); QAbstractListModel *model = qobject_cast(object); QVERIFY(model != 0); QCOMPARE(model->property("count").toInt(), 2); /* Create a second account */ Account *account2 = manager->createAccount("bad"); QVERIFY(account2 != 0); account2->setEnabled(false); account2->setDisplayName("BadAccount"); account2->selectService(badMail); account2->setEnabled(true); account2->selectService(badShare); account2->setEnabled(false); account2->syncAndBlock(); QSignalSpy countChanged(model, SIGNAL(countChanged())); countChanged.wait(); QCOMPARE(model->property("count").toInt(), 4); QCOMPARE(get(model, 0, "displayName").toString(), QString("BadAccount")); QCOMPARE(get(model, 1, "displayName").toString(), QString("BadAccount")); QCOMPARE(get(model, 2, "displayName").toString(), QString("CoolAccount")); QCOMPARE(get(model, 3, "displayName").toString(), QString("CoolAccount")); /* Change the displayName, and verify that the dataChanged() signal is * emitted */ QSignalSpy dataChanged(model, SIGNAL(dataChanged(const QModelIndex&,const QModelIndex&))); account1->setDisplayName("ColdAccount"); account1->syncAndBlock(); dataChanged.wait(); /* This is actually an implementation detail: instead of a single signal * carrying the index range in its parameters, we currently get N separate * signals. */ QCOMPARE(dataChanged.count(), 2); QModelIndex index = qvariant_cast(dataChanged.at(0).at(0)); QCOMPARE(index.row(), 2); index = qvariant_cast(dataChanged.at(1).at(0)); QCOMPARE(index.row(), 3); QCOMPARE(get(model, 0, "displayName").toString(), QString("BadAccount")); QCOMPARE(get(model, 1, "displayName").toString(), QString("BadAccount")); QCOMPARE(get(model, 2, "displayName").toString(), QString("ColdAccount")); QCOMPARE(get(model, 3, "displayName").toString(), QString("ColdAccount")); delete manager; delete object; } void PluginTest::testProviderModel() { /* Create some accounts */ Manager *manager = new Manager(this); ProviderList providers = manager->providerList(); QQmlEngine engine; QQmlComponent component(&engine); component.setData("import Ubuntu.OnlineAccounts 0.1\n" "ProviderModel {}", QUrl()); QObject *object = component.create(); QVERIFY(object != 0); QAbstractListModel *model = qobject_cast(object); QVERIFY(model != 0); QCOMPARE(model->rowCount(), providers.count()); QCOMPARE(model->property("count").toInt(), providers.count()); QCOMPARE(model->property("applicationId").toString(), QString()); for (int i = 0; i < providers.count(); i++) { QCOMPARE(get(model, i, "displayName").toString(), providers[i].displayName()); QCOMPARE(get(model, i, "providerId").toString(), providers[i].name()); QCOMPARE(get(model, i, "iconName").toString(), providers[i].iconName()); QCOMPARE(get(model, i, "isSingleAccount").toBool(), providers[i].isSingleAccount()); QCOMPARE(get(model, i, "translations").toString(), providers[i].trCatalog()); } QCOMPARE(get(model, 100, "iconName"), QVariant()); QVariant value; QVERIFY(QMetaObject::invokeMethod(model, "get", Q_RETURN_ARG(QVariant, value), Q_ARG(int, 1), Q_ARG(QString, "providerId"))); QCOMPARE(value.toString(), providers[1].name()); delete manager; delete object; } void PluginTest::testProviderModelWithApplication() { /* Create some accounts */ Manager *manager = new Manager(this); ProviderList providers = manager->providerList(); QQmlEngine engine; QQmlComponent component(&engine); component.setData("import Ubuntu.OnlineAccounts 0.1\n" "ProviderModel {\n" " applicationId: \"mailer\"\n" "}", QUrl()); QObject *object = component.create(); QVERIFY(object != 0); QAbstractListModel *model = qobject_cast(object); QVERIFY(model != 0); QCOMPARE(model->rowCount(), providers.count()); QCOMPARE(model->property("count").toInt(), providers.count()); QCOMPARE(model->property("applicationId").toString(), QString("mailer")); /* Now set an application which supports only "coolservice" and verify that * only the "cool" provider is there */ QSignalSpy countSignal(model, SIGNAL(countChanged())); model->setProperty("applicationId", QString("coolpublisher")); QCOMPARE(model->property("applicationId").toString(), QString("coolpublisher")); QCOMPARE(countSignal.count(), 1); QCOMPARE(model->property("count").toInt(), 1); /* Do it twice, just to improve branch coverage */ model->setProperty("applicationId", QString("coolpublisher")); QCOMPARE(get(model, 0, "providerId").toString(), QString("cool")); delete manager; delete object; } void PluginTest::testAccountService() { clearDb(); /* Create one account */ Manager *manager = new Manager(this); Service coolMail = manager->service("coolmail"); Service coolShare = manager->service("coolshare"); Service badMail = manager->service("badmail"); Service badShare = manager->service("badshare"); Account *account1 = manager->createAccount("cool"); QVERIFY(account1 != 0); account1->setEnabled(true); account1->setDisplayName("CoolAccount"); account1->selectService(coolMail); account1->setEnabled(true); account1->selectService(coolShare); account1->setEnabled(false); account1->syncAndBlock(); AccountService *accountService1 = new AccountService(account1, coolMail); QVERIFY(accountService1 != 0); QQmlEngine engine; engine.rootContext()->setContextProperty("accountService1", accountService1); QQmlComponent component(&engine); component.setData("import Ubuntu.OnlineAccounts 0.1\n" "AccountService { objectHandle: accountService1 }", QUrl()); QObject *qmlObject = component.create(); QVERIFY(qmlObject != 0); QCOMPARE(qmlObject->property("objectHandle").value(), accountService1); QCOMPARE(qmlObject->property("enabled").toBool(), true); QCOMPARE(qmlObject->property("serviceEnabled").toBool(), true); QCOMPARE(qmlObject->property("displayName").toString(), QString("CoolAccount")); QCOMPARE(qmlObject->property("accountId").toUInt(), account1->id()); QVariantMap provider = qmlObject->property("provider").toMap(); QVERIFY(!provider.isEmpty()); QCOMPARE(provider["id"].toString(), QString("cool")); QCOMPARE(provider["displayName"].toString(), QString("Cool provider")); QCOMPARE(provider["iconName"].toString(), QString("general_myprovider")); QCOMPARE(provider["isSingleAccount"].toBool(), true); QCOMPARE(provider["translations"].toString(), QString("somewhere")); QVariantMap service = qmlObject->property("service").toMap(); QVERIFY(!service.isEmpty()); QCOMPARE(service["id"].toString(), QString("coolmail")); QCOMPARE(service["displayName"].toString(), QString("Cool Mail")); QCOMPARE(service["iconName"].toString(), QString("general_myservice")); QCOMPARE(service["serviceTypeId"].toString(), QString("e-mail")); QCOMPARE(service["translations"].toString(), QString("here")); QVariantMap settings = qmlObject->property("settings").toMap(); QVERIFY(!settings.isEmpty()); QCOMPARE(settings["color"].toString(), QString("green")); QCOMPARE(settings["auto-explode-after"].toUInt(), uint(10)); QCOMPARE(settings.count(), 2); QVariantMap authData = qmlObject->property("authData").toMap(); QVERIFY(!authData.isEmpty()); QCOMPARE(authData["method"].toString(), QString("oauth2")); QCOMPARE(authData["mechanism"].toString(), QString("user_agent")); QVariantMap parameters = authData["parameters"].toMap(); QVERIFY(!parameters.isEmpty()); QCOMPARE(parameters["host"].toString(), QString("coolmail.ex")); /* Delete the account service, and check that the QML object survives */ delete accountService1; QCOMPARE(qmlObject->property("objectHandle").value(), (AccountService*)0); QCOMPARE(qmlObject->property("enabled").toBool(), false); QCOMPARE(qmlObject->property("serviceEnabled").toBool(), false); QCOMPARE(qmlObject->property("displayName").toString(), QString()); QCOMPARE(qmlObject->property("accountId").toUInt(), uint(0)); provider = qmlObject->property("provider").toMap(); QVERIFY(provider.isEmpty()); service = qmlObject->property("service").toMap(); QVERIFY(service.isEmpty()); settings = qmlObject->property("settings").toMap(); QVERIFY(settings.isEmpty()); authData = qmlObject->property("authData").toMap(); QVERIFY(authData.isEmpty()); QVariantMap newSettings; newSettings.insert("color", QString("red")); bool ok; ok = QMetaObject::invokeMethod(qmlObject, "updateSettings", Q_ARG(QVariantMap, newSettings)); QVERIFY(ok); ok = QMetaObject::invokeMethod(qmlObject, "updateServiceEnabled", Q_ARG(bool, true)); QVERIFY(ok); delete manager; delete qmlObject; } void PluginTest::testAccountServiceUpdate() { clearDb(); /* Create one account */ Manager *manager = new Manager(this); Service coolMail = manager->service("coolmail"); Account *account = manager->createAccount("cool"); QVERIFY(account != 0); account->setEnabled(true); account->setDisplayName("CoolAccount"); account->selectService(coolMail); account->setEnabled(true); account->syncAndBlock(); AccountService *accountService = new AccountService(account, coolMail); QVERIFY(accountService != 0); QQmlEngine engine; engine.rootContext()->setContextProperty("accountService", accountService); QQmlComponent component(&engine); component.setData("import Ubuntu.OnlineAccounts 0.1\n" "AccountService { objectHandle: accountService }", QUrl()); QObject *qmlObject = component.create(); QVERIFY(qmlObject != 0); QCOMPARE(qmlObject->property("objectHandle").value(), accountService); QCOMPARE(qmlObject->property("autoSync").toBool(), true); /* Set it to the same value, just to increase coverage */ QVERIFY(qmlObject->setProperty("autoSync", true)); QCOMPARE(qmlObject->property("autoSync").toBool(), true); QVariantMap settings = qmlObject->property("settings").toMap(); QVERIFY(!settings.isEmpty()); QCOMPARE(settings["color"].toString(), QString("green")); QCOMPARE(settings["auto-explode-after"].toUInt(), uint(10)); QCOMPARE(settings.count(), 2); QSignalSpy settingsChanged(qmlObject, SIGNAL(settingsChanged())); /* Update a couple of settings */ QVariantMap newSettings; newSettings.insert("color", QString("red")); newSettings.insert("verified", true); QMetaObject::invokeMethod(qmlObject, "updateSettings", Q_ARG(QVariantMap, newSettings)); QTest::qWait(50); QCOMPARE(settingsChanged.count(), 1); settingsChanged.clear(); settings = qmlObject->property("settings").toMap(); QCOMPARE(settings["color"].toString(), QString("red")); QCOMPARE(settings["auto-explode-after"].toUInt(), uint(10)); QCOMPARE(settings["verified"].toBool(), true); QCOMPARE(settings.count(), 3); /* Disable the service */ QSignalSpy enabledChanged(qmlObject, SIGNAL(enabledChanged())); QMetaObject::invokeMethod(qmlObject, "updateServiceEnabled", Q_ARG(bool, false)); QTest::qWait(50); QCOMPARE(enabledChanged.count(), 1); enabledChanged.clear(); QCOMPARE(qmlObject->property("enabled").toBool(), false); QCOMPARE(settingsChanged.count(), 1); settingsChanged.clear(); QCOMPARE(qmlObject->property("serviceEnabled").toBool(), false); /* Disable autoSync, and change something else */ qmlObject->setProperty("autoSync", false); QCOMPARE(qmlObject->property("autoSync").toBool(), false); newSettings.clear(); newSettings.insert("verified", false); newSettings.insert("color", QVariant()); QMetaObject::invokeMethod(qmlObject, "updateSettings", Q_ARG(QVariantMap, newSettings)); QTest::qWait(50); /* Nothing should have been changed yet */ QCOMPARE(settingsChanged.count(), 0); settings = qmlObject->property("settings").toMap(); QCOMPARE(settings["verified"].toBool(), true); /* Manually store the settings */ account->sync(); QTest::qWait(50); QCOMPARE(settingsChanged.count(), 1); settingsChanged.clear(); settings = qmlObject->property("settings").toMap(); QCOMPARE(settings["verified"].toBool(), false); QCOMPARE(settings["color"].toString(), QString("green")); delete accountService; delete manager; delete qmlObject; } void PluginTest::testAuthentication_data() { QTest::addColumn("params"); QTest::addColumn("expectedReply"); QVariantMap reply; reply.insert("test", QString("OK")); reply.insert("host", QString("coolmail.ex")); QTest::newRow("success with params") << "{ \"test\": \"OK\" }" << reply; reply.clear(); reply.insert("host", QString("coolmail.ex")); QTest::newRow("success without params") << "" << reply; reply.clear(); } void PluginTest::testAuthentication() { QFETCH(QString, params); QFETCH(QVariantMap, expectedReply); clearDb(); /* Create one account */ Manager *manager = new Manager(this); Service coolMail = manager->service("coolmail"); Service coolShare = manager->service("coolshare"); Account *account1 = manager->createAccount("cool"); QVERIFY(account1 != 0); account1->setEnabled(true); account1->setDisplayName("CoolAccount"); account1->selectService(coolMail); account1->setEnabled(true); account1->selectService(coolShare); account1->setEnabled(false); account1->syncAndBlock(); AccountService *accountService1 = new AccountService(account1, coolMail); QVERIFY(accountService1 != 0); QQmlEngine engine; engine.rootContext()->setContextProperty("accountService1", accountService1); QQmlComponent component(&engine); component.setData("import Ubuntu.OnlineAccounts 0.1\n" "AccountService {\n" " objectHandle: accountService1\n" " function go() {\n" " authenticate(" + params.toUtf8() + ")\n" " }\n" "}", QUrl()); QObject *qmlObject = component.create(); QVERIFY(qmlObject != 0); QSignalSpy authenticated(qmlObject, SIGNAL(authenticated(const QVariantMap &))); QSignalSpy authenticationError(qmlObject, SIGNAL(authenticationError(const QVariantMap &))); QMetaObject::invokeMethod(qmlObject, "go"); QTRY_COMPARE(authenticated.count(), 1); QCOMPARE(authenticationError.count(), 0); QVariantMap reply = authenticated.at(0).at(0).toMap(); QVERIFY(mapIsSubset(expectedReply, reply)); delete accountService1; delete manager; delete qmlObject; } void PluginTest::testAuthenticationErrors_data() { QTest::addColumn("params"); QTest::addColumn("codeName"); QTest::addColumn("expectedMessage"); QTest::newRow("Signon::UserCanceled") << "{ \"errorCode\": 311, \"errorMessage\": \"Failed!\" }" << "UserCanceledError" << "Failed!"; QTest::newRow("Signon::InvalidQuery") << "{ \"errorCode\": 103, \"errorMessage\": \"Weird\" }" << "NoAccountError" << "Weird"; QTest::newRow("Signon::PermissionDenied") << "{ \"errorCode\": 4, \"errorMessage\": \"Failed!\" }" << "PermissionDeniedError" << "Failed!"; QTest::newRow("Signon::NoConnection") << "{ \"errorCode\": 307, \"errorMessage\": \"Failed!\" }" << "NetworkError" << "Failed!"; } void PluginTest::testAuthenticationErrors() { QFETCH(QString, params); QFETCH(QString, codeName); QFETCH(QString, expectedMessage); clearDb(); /* Create one account */ Manager *manager = new Manager(this); Service coolMail = manager->service("coolmail"); Service coolShare = manager->service("coolshare"); Account *account1 = manager->createAccount("cool"); QVERIFY(account1 != 0); account1->setEnabled(true); account1->setDisplayName("CoolAccount"); account1->selectService(coolMail); account1->setEnabled(true); account1->selectService(coolShare); account1->setEnabled(false); account1->syncAndBlock(); AccountService *accountService1 = new AccountService(account1, coolMail); QVERIFY(accountService1 != 0); QQmlEngine engine; engine.rootContext()->setContextProperty("accountService1", accountService1); QQmlComponent component(&engine); component.setData("import Ubuntu.OnlineAccounts 0.1\n" "AccountService {\n" " objectHandle: accountService1\n" " function go() {\n" " authenticate(" + params.toUtf8() + ")\n" " }\n" "}", QUrl()); QObject *qmlObject = component.create(); qDebug() << component.errors(); QVERIFY(qmlObject != 0); QSignalSpy authenticationError(qmlObject, SIGNAL(authenticationError(const QVariantMap &))); QMetaObject::invokeMethod(qmlObject, "go"); QTRY_COMPARE(authenticationError.count(), 1); QVariantMap reply = authenticationError.at(0).at(0).toMap(); int code = reply.value("code").toInt(); QString message = reply.value("message").toString(); QCOMPARE(message, expectedMessage); const QMetaObject *mo = qmlObject->metaObject(); const QMetaEnum errorEnum = mo->enumerator(mo->indexOfEnumerator("ErrorCode")); QCOMPARE(code, errorEnum.keyToValue(codeName.toUtf8().constData())); delete accountService1; delete manager; delete qmlObject; } void PluginTest::testAuthenticationDeleted() { clearDb(); /* Create one account */ Manager *manager = new Manager(this); Service coolMail = manager->service("coolmail"); Service coolShare = manager->service("coolshare"); Account *account1 = manager->createAccount("cool"); QVERIFY(account1 != 0); account1->setEnabled(true); account1->setDisplayName("CoolAccount"); account1->selectService(coolMail); account1->setEnabled(true); account1->selectService(coolShare); account1->setEnabled(false); account1->syncAndBlock(); AccountService *accountService1 = new AccountService(account1, coolMail); QVERIFY(accountService1 != 0); QQmlEngine engine; engine.rootContext()->setContextProperty("accountService1", accountService1); QQmlComponent component(&engine); component.setData("import Ubuntu.OnlineAccounts 0.1\n" "AccountService { objectHandle: accountService1 }", QUrl()); QObject *qmlObject = component.create(); QVERIFY(qmlObject != 0); QSignalSpy authenticated(qmlObject, SIGNAL(authenticated(const QVariantMap &))); QSignalSpy authenticationError(qmlObject, SIGNAL(authenticationError(const QVariantMap &))); QVariantMap sessionData; QVariantMap error; /* Delete the account service, and check that the QML object survives */ delete accountService1; QCOMPARE(qmlObject->property("objectHandle").value(), (AccountService*)0); /* Authenticate now: we should get an error */ sessionData.clear(); sessionData.insert("test", QString("OK")); QMetaObject::invokeMethod(qmlObject, "authenticate", Q_ARG(QVariantMap, sessionData)); QTest::qWait(50); QCOMPARE(authenticationError.count(), 1); QCOMPARE(authenticated.count(), 0); error = authenticationError.at(0).at(0).toMap(); QVERIFY(!error.isEmpty()); QCOMPARE(error["message"].toString(), QString("Invalid AccountService")); authenticationError.clear(); delete manager; delete qmlObject; } void PluginTest::testAuthenticationCancel() { clearDb(); /* Create one account */ Manager *manager = new Manager(this); Service coolMail = manager->service("coolmail"); Account *account1 = manager->createAccount("cool"); QVERIFY(account1 != 0); account1->setEnabled(true); account1->setDisplayName("CoolAccount"); account1->selectService(coolMail); account1->setEnabled(true); account1->syncAndBlock(); AccountService *accountService1 = new AccountService(account1, coolMail); QVERIFY(accountService1 != 0); QQmlEngine engine; engine.rootContext()->setContextProperty("accountService1", accountService1); QQmlComponent component(&engine); component.setData("import Ubuntu.OnlineAccounts 0.1\n" "AccountService { objectHandle: accountService1 }", QUrl()); QObject *qmlObject = component.create(); QVERIFY(qmlObject != 0); QSignalSpy authenticated(qmlObject, SIGNAL(authenticated(const QVariantMap &))); QSignalSpy authenticationError(qmlObject, SIGNAL(authenticationError(const QVariantMap &))); /* First, check that calling cancelAuthentication() on an idle object * doesn't do anything. */ bool ok = QMetaObject::invokeMethod(qmlObject, "cancelAuthentication"); QVERIFY(ok); QTest::qWait(50); QCOMPARE(authenticated.count(), 0); QCOMPARE(authenticationError.count(), 0); /* Now, try canceling a session. */ QVariantMap sessionData; sessionData.insert("test", QString("OK")); QMetaObject::invokeMethod(qmlObject, "authenticate", Q_ARG(QVariantMap, sessionData)); QTest::qWait(2); QCOMPARE(authenticated.count(), 0); QCOMPARE(authenticationError.count(), 0); ok = QMetaObject::invokeMethod(qmlObject, "cancelAuthentication"); QVERIFY(ok); QTest::qWait(50); QCOMPARE(authenticated.count(), 0); QCOMPARE(authenticationError.count(), 1); QVariantMap error = authenticationError.at(0).at(0).toMap(); QVERIFY(!error.isEmpty()); const QMetaObject *mo = qmlObject->metaObject(); const QMetaEnum errorEnum = mo->enumerator(mo->indexOfEnumerator("ErrorCode")); QCOMPARE(error["code"].toInt(), errorEnum.keyToValue("UserCanceledError")); authenticationError.clear(); delete accountService1; delete manager; delete qmlObject; } void PluginTest::testAuthenticationWithCredentials() { /* Create one account */ Manager *manager = new Manager(this); Service coolMail = manager->service("coolmail"); Account *account = manager->createAccount("cool"); QVERIFY(account != 0); AccountService *accountService = new AccountService(account, coolMail); QVERIFY(accountService != 0); QQmlEngine engine; engine.rootContext()->setContextProperty("accountService", accountService); QQmlComponent component(&engine); component.setData("import Ubuntu.OnlineAccounts 0.1\n" "AccountService {\n" " id: account\n" " autoSync: false\n" " objectHandle: accountService\n" " credentials: Credentials {\n" " objectName: \"creds\"\n" " userName: \"Happy user\"\n" " caption: account.provider.displayName\n" " }\n" "}", QUrl()); QObject *qmlAccount = component.create(); QVERIFY(qmlAccount != 0); QSignalSpy authenticated(qmlAccount, SIGNAL(authenticated(const QVariantMap &))); QSignalSpy authenticationError(qmlAccount, SIGNAL(authenticationError(const QVariantMap &))); QObject *qmlCredentials = qmlAccount->findChild("creds"); QVERIFY(qmlCredentials != 0); /* Store the credentials */ QSignalSpy synced(qmlCredentials, SIGNAL(synced())); bool ok; ok = QMetaObject::invokeMethod(qmlCredentials, "sync"); QVERIFY(ok); /* Wait for the operation to finish, and verify it succeeded */ QTest::qWait(100); QCOMPARE(synced.count(), 1); synced.clear(); uint credentialsId = qmlCredentials->property("credentialsId").toUInt(); QVERIFY(credentialsId != 0); QVariantMap sessionData; sessionData.insert("test", QString("OK")); QMetaObject::invokeMethod(qmlAccount, "authenticate", Q_ARG(QVariantMap, sessionData)); QTest::qWait(50); QCOMPARE(authenticationError.count(), 0); QCOMPARE(authenticated.count(), 1); QVariantMap reply = authenticated.at(0).at(0).toMap(); QVERIFY(!reply.isEmpty()); QCOMPARE(reply["test"].toString(), QString("OK")); QCOMPARE(reply["host"].toString(), QString("coolmail.ex")); /* Check that the newly created credentials were used */ QCOMPARE(reply["credentialsId"].toUInt(), credentialsId); authenticated.clear(); delete manager; delete qmlAccount; } void PluginTest::testManagerCreate() { QQmlEngine engine; QQmlComponent component(&engine); component.setData("import Ubuntu.OnlineAccounts 0.1\n" "Account { objectHandle: Manager.createAccount(\"cool\") }", QUrl()); QObject *qmlObject = component.create(); QVERIFY(qmlObject != 0); QVariantMap provider = qmlObject->property("provider").toMap(); QVERIFY(!provider.isEmpty()); QCOMPARE(provider["id"].toString(), QString("cool")); QCOMPARE(provider["displayName"].toString(), QString("Cool provider")); QCOMPARE(provider["iconName"].toString(), QString("general_myprovider")); delete qmlObject; } void PluginTest::testManagerLoad() { clearDb(); /* Create one account */ Manager *manager = new Manager(this); Account *account1 = manager->createAccount("cool"); QVERIFY(account1 != 0); account1->syncAndBlock(); QVERIFY(account1->id() != 0); QQmlEngine engine; engine.rootContext()->setContextProperty("account1id", uint(account1->id())); QQmlComponent component(&engine); component.setData("import Ubuntu.OnlineAccounts 0.1\n" "Account { objectHandle: Manager.loadAccount(account1id) }", QUrl()); QObject *qmlObject = component.create(); QVERIFY(qmlObject != 0); QCOMPARE(qmlObject->property("accountId").toUInt(), account1->id()); QVariantMap provider = qmlObject->property("provider").toMap(); QVERIFY(!provider.isEmpty()); QCOMPARE(provider["id"].toString(), QString("cool")); QCOMPARE(provider["displayName"].toString(), QString("Cool provider")); QCOMPARE(provider["iconName"].toString(), QString("general_myprovider")); delete manager; delete qmlObject; } void PluginTest::testAccountInvalid() { QQmlEngine engine; QQmlComponent component(&engine); component.setData("import Ubuntu.OnlineAccounts 0.1\n" "Account {}", QUrl()); QObject *qmlObject = component.create(); QVERIFY(qmlObject != 0); QVERIFY(qmlObject->property("objectHandle").value() == 0); QCOMPARE(qmlObject->property("enabled").toBool(), false); QCOMPARE(qmlObject->property("displayName").toString(), QString()); QCOMPARE(qmlObject->property("accountId").toUInt(), uint(0)); QVariantMap provider = qmlObject->property("provider").toMap(); QVERIFY(provider.isEmpty()); qmlObject->setProperty("objectHandle", QVariant::fromValue(0)); QVERIFY(qmlObject->property("objectHandle").value() == 0); bool ok; ok = QMetaObject::invokeMethod(qmlObject, "updateDisplayName", Q_ARG(QString, "dummy")); QVERIFY(ok); ok = QMetaObject::invokeMethod(qmlObject, "updateEnabled", Q_ARG(bool, "true")); QVERIFY(ok); ok = QMetaObject::invokeMethod(qmlObject, "sync"); QVERIFY(ok); ok = QMetaObject::invokeMethod(qmlObject, "remove"); QVERIFY(ok); delete qmlObject; } void PluginTest::testAccount() { clearDb(); QQmlEngine engine; QQmlComponent component(&engine); component.setData("import Ubuntu.OnlineAccounts 0.1\n" "Account { objectHandle: Manager.createAccount(\"cool\") }", QUrl()); QObject *qmlObject = component.create(); QVERIFY(qmlObject != 0); QObject *objectHandle = qmlObject->property("objectHandle").value(); Account *account = qobject_cast(objectHandle); QVERIFY(account != 0); QVERIFY(account->id() == 0); QVariantMap provider = qmlObject->property("provider").toMap(); QVERIFY(!provider.isEmpty()); QCOMPARE(provider["id"].toString(), QString("cool")); bool ok; ok = QMetaObject::invokeMethod(qmlObject, "updateDisplayName", Q_ARG(QString, "new name")); QVERIFY(ok); ok = QMetaObject::invokeMethod(qmlObject, "updateEnabled", Q_ARG(bool, "true")); QVERIFY(ok); ok = QMetaObject::invokeMethod(qmlObject, "sync"); QVERIFY(ok); QTest::qWait(50); /* Check that the changes have been recorded */ QVERIFY(account->id() != 0); AccountId accountId = account->id(); QCOMPARE(qmlObject->property("accountId").toUInt(), uint(account->id())); QCOMPARE(qmlObject->property("displayName").toString(), QString("new name")); QCOMPARE(qmlObject->property("enabled").toBool(), true); objectHandle = qmlObject->property("accountServiceHandle").value(); AccountService *accountService = qobject_cast(objectHandle); QVERIFY(accountService != 0); /* Set the same account instance on the account; just to improve coverage * of branches. */ ok = qmlObject->setProperty("objectHandle", QVariant::fromValue(account)); QVERIFY(ok); /* Delete the account */ ok = QMetaObject::invokeMethod(qmlObject, "remove"); QVERIFY(ok); QTest::qWait(50); /* Check that the account has effectively been removed */ Manager *manager = new Manager(this); Account *account1 = manager->account(accountId); QVERIFY(account1 == 0); delete qmlObject; } void PluginTest::testCredentials() { QQmlEngine engine; QQmlComponent component(&engine); component.setData("import Ubuntu.OnlineAccounts 0.1\n" "Credentials {" " userName: \"Smart User\"\n" " secret: \"Valuable password\"\n" " storeSecret: true\n" " caption: \"Service One\"\n" " acl: [ \"Me\", \"You\" ]\n" " methods: {\n" " \"oauth\": [ \"one\", \"two\" ]," " \"sasl\": [ \"plain\" ]" " }\n" "}", QUrl()); QObject *qmlObject = component.create(); QVERIFY(qmlObject != 0); QCOMPARE(qmlObject->property("userName").toString(), QString("Smart User")); QCOMPARE(qmlObject->property("secret").toString(), QString("Valuable password")); QCOMPARE(qmlObject->property("storeSecret").toBool(), true); QCOMPARE(qmlObject->property("caption").toString(), QString("Service One")); QStringList acl; acl << "Me" << "You"; QCOMPARE(qmlObject->property("acl").toStringList(), acl); QVariantMap methods; methods.insert("oauth", QStringList() << "one" << "two"); methods.insert("sasl", QStringList() << "plain"); QCOMPARE(qmlObject->property("methods").toMap(), methods); QCOMPARE(qmlObject->property("credentialsId").toUInt(), uint(0)); /* Set a few fields to the same values, just to increase coverage of * branches */ qmlObject->setProperty("credentialsId", uint(0)); qmlObject->setProperty("userName", "Smart User"); qmlObject->setProperty("secret", "Valuable password"); qmlObject->setProperty("storeSecret", true); qmlObject->setProperty("caption", "Service One"); qmlObject->setProperty("methods", methods); qmlObject->setProperty("acl", acl); /* Remove the credentials; this won't do anything now */ bool ok; ok = QMetaObject::invokeMethod(qmlObject, "remove"); QVERIFY(ok); /* Store the credentials */ QSignalSpy synced(qmlObject, SIGNAL(synced())); QSignalSpy credentialsIdChanged(qmlObject, SIGNAL(credentialsIdChanged())); ok = QMetaObject::invokeMethod(qmlObject, "sync"); QVERIFY(ok); QTest::qWait(100); QCOMPARE(synced.count(), 1); synced.clear(); QCOMPARE(credentialsIdChanged.count(), 1); credentialsIdChanged.clear(); uint credentialsId = qmlObject->property("credentialsId").toUInt(); QVERIFY(credentialsId != 0); engine.rootContext()->setContextProperty("credsId", credentialsId); component.setData("import Ubuntu.OnlineAccounts 0.1\n" "Credentials { credentialsId: credsId }", QUrl()); QObject *qmlObject2 = component.create(); QVERIFY(qmlObject2 != 0); QCOMPARE(qmlObject2->property("credentialsId").toUInt(), credentialsId); /* After some time, we should get the synced() signal and all fields should * be loaded at that point */ QSignalSpy synced2(qmlObject2, SIGNAL(synced())); QTest::qWait(100); QCOMPARE(synced2.count(), 1); synced2.clear(); QCOMPARE(qmlObject2->property("userName").toString(), QString("Smart User")); /* Set the credentialsId to 0, everything should continue to work */ qmlObject2->setProperty("credentialsId", uint(0)); QTest::qWait(100); QCOMPARE(qmlObject2->property("userName").toString(), QString("Smart User")); /* test removal of the credentials */ QSignalSpy removed(qmlObject, SIGNAL(removed())); ok = QMetaObject::invokeMethod(qmlObject, "remove"); QVERIFY(ok); QTest::qWait(100); QCOMPARE(removed.count(), 1); removed.clear(); delete qmlObject2; delete qmlObject; } void PluginTest::testAccountCredentialsRemoval_data() { QTest::addColumn("removeCredentials"); QTest::addColumn("expectedUserName"); QTest::newRow("With credentials removal") << true << QString(); QTest::newRow("Without credentials removal") << false << QString("Happy user"); } void PluginTest::testAccountCredentialsRemoval() { QFETCH(bool, removeCredentials); QFETCH(QString, expectedUserName); clearDb(); /* Create one account */ Manager *manager = new Manager(this); Service coolMail = manager->service("coolmail"); Account *account = manager->createAccount("cool"); QVERIFY(account != 0); account->setEnabled(true); account->setDisplayName("CoolAccount"); account->selectService(coolMail); account->setEnabled(true); account->syncAndBlock(); quint32 accountId = account->id(); QVERIFY(accountId != 0); AccountService *globalService = new AccountService(account, Service()); QVERIFY(globalService != 0); AccountService *mailService = new AccountService(account, coolMail); QVERIFY(mailService != 0); QList services; services.append(globalService); services.append(mailService); QQmlEngine engine; Q_FOREACH (AccountService *accountService, services) { engine.rootContext()->setContextProperty("accountService", accountService); QQmlComponent component(&engine); component.setData("import Ubuntu.OnlineAccounts 0.1\n" "AccountService {\n" " id: account\n" " objectHandle: accountService\n" " credentials: Credentials {\n" " objectName: \"creds\"\n" " userName: \"Happy user\"\n" " caption: account.provider.displayName\n" " }\n" "}", QUrl()); QObject *qmlAccount = component.create(); QVERIFY(qmlAccount != 0); QObject *qmlCredentials = qmlAccount->findChild("creds"); QVERIFY(qmlCredentials != 0); /* Store the credentials */ bool ok; ok = QMetaObject::invokeMethod(qmlCredentials, "sync"); QVERIFY(ok); QTest::qWait(100); } /* And check that the credentialsId have now been written to the account*/ uint globalCredentialsId = globalService->value("CredentialsId").toUInt(); QVERIFY(globalCredentialsId != 0); uint mailCredentialsId = mailService->value("CredentialsId").toUInt(); QVERIFY(mailCredentialsId != 0); QVERIFY(mailCredentialsId != globalCredentialsId); delete globalService; delete mailService; delete manager; QQmlComponent accountComponent(&engine); engine.rootContext()->setContextProperty("aid", accountId); accountComponent.setData("import Ubuntu.OnlineAccounts 0.1\n" "Account {\n" " objectHandle: Manager.loadAccount(aid)\n" " function removeAccount1() {\n" " remove(Account.RemoveAccountOnly)\n" " }\n" " function removeAccount2() {\n" " remove(Account.RemoveCredentials)\n" " }\n" "}", QUrl()); QObject *qmlAccount = accountComponent.create(); QVERIFY(qmlAccount != 0); /* test removal of the credentials */ QSignalSpy removed(qmlAccount, SIGNAL(removed())); const char *removeFunction = removeCredentials ? "removeAccount2" : "removeAccount1"; bool ok = QMetaObject::invokeMethod(qmlAccount, removeFunction); QVERIFY(ok); QTest::qWait(200); QCOMPARE(removed.count(), 1); removed.clear(); delete qmlAccount; /* Verify that the credentials have actually been removed if * removeCredentials was true, or retained if it was false. */ QList credentials; credentials.append(globalCredentialsId); credentials.append(mailCredentialsId); Q_FOREACH (uint credentialsId, credentials) { engine.rootContext()->setContextProperty("credsId", credentialsId); QQmlComponent component(&engine); component.setData("import Ubuntu.OnlineAccounts 0.1\n" "Credentials { credentialsId: credsId }", QUrl()); QObject *qmlCredentials = component.create(); QVERIFY(qmlCredentials != 0); QTest::qWait(100); QCOMPARE(qmlCredentials->property("userName").toString(), expectedUserName); } } void PluginTest::testAccountServiceCredentials() { clearDb(); /* Create one account */ Manager *manager = new Manager(this); Service coolMail = manager->service("coolmail"); Account *account = manager->createAccount("cool"); QVERIFY(account != 0); account->setEnabled(true); account->setDisplayName("CoolAccount"); account->selectService(coolMail); account->setEnabled(true); account->syncAndBlock(); AccountService *accountService = new AccountService(account, coolMail); QVERIFY(accountService != 0); QQmlEngine engine; engine.rootContext()->setContextProperty("accountService", accountService); QQmlComponent component(&engine); component.setData("import Ubuntu.OnlineAccounts 0.1\n" "AccountService {\n" " id: account\n" " objectHandle: accountService\n" " credentials: Credentials {\n" " objectName: \"creds\"\n" " userName: \"Happy user\"\n" " caption: account.provider.displayName\n" " }\n" "}", QUrl()); QObject *qmlAccount = component.create(); QVERIFY(qmlAccount != 0); QObject *qmlCredentials = qmlAccount->findChild("creds"); QVERIFY(qmlCredentials != 0); /* Sanity check */ QCOMPARE(qmlAccount->property("credentials").value(), qmlCredentials); QCOMPARE(qmlCredentials->property("userName").toString(), QString("Happy user")); QCOMPARE(qmlCredentials->property("caption").toString(), QString("Cool provider")); /* Store the credentials */ QSignalSpy synced(qmlCredentials, SIGNAL(synced())); bool ok; ok = QMetaObject::invokeMethod(qmlCredentials, "sync"); QVERIFY(ok); /* Wait for the operation to finish, and verify it succeeded */ QTest::qWait(100); QCOMPARE(synced.count(), 1); synced.clear(); uint credentialsId = qmlCredentials->property("credentialsId").toUInt(); QVERIFY(credentialsId != 0); /* Verify that autoSync is true (it should always be true by default */ QCOMPARE(qmlAccount->property("autoSync").toBool(), true); /* And check that the credentialsId have now been written to the account*/ QVariantMap authData = qmlAccount->property("authData").toMap(); QVERIFY(!authData.isEmpty()); QCOMPARE(authData["credentialsId"].toUInt(), credentialsId); /* Just to increase coverage */ qmlAccount->setProperty("credentials", QVariant::fromValue(qmlCredentials)); qmlAccount->setProperty("credentials", QVariant::fromValue(0)); QVERIFY(qmlAccount->property("credentials").value() == 0); delete qmlAccount; delete accountService; } void PluginTest::testApplicationModel() { QQmlEngine engine; QQmlComponent component(&engine); component.setData("import Ubuntu.OnlineAccounts 0.1\n" "ApplicationModel {}", QUrl()); QObject *qmlModel = component.create(); QVERIFY(qmlModel != 0); QAbstractListModel *model = qobject_cast(qmlModel); QVERIFY(model != 0); QCOMPARE(model->rowCount(), 0); /* Retrieve a couple of invalid indexes */ QVERIFY(!get(model, 0, "applicationId").isValid()); QVERIFY(!get(model, -1, "applicationId").isValid()); /* Set a valid service on the model */ qmlModel->setProperty("service", QString("badmail")); QCOMPARE(model->property("service").toString(), QString("badmail")); QCOMPARE(model->rowCount(), 1); QCOMPARE(model->property("count").toInt(), 1); QCOMPARE(get(model, 0, "applicationId").toString(), QString("mailer")); QCOMPARE(get(model, 0, "displayName").toString(), QString("Easy Mailer")); QCOMPARE(get(model, 0, "iconName").toString(), QString("mailer-icon")); QCOMPARE(get(model, 0, "serviceUsage").toString(), QString("Mailer can retrieve your e-mails")); QVariant value; QVERIFY(QMetaObject::invokeMethod(model, "get", Q_RETURN_ARG(QVariant, value), Q_ARG(int, 0), Q_ARG(QString, "applicationId"))); QCOMPARE(value.toString(), QString("mailer")); /* Get an Application from the model */ QObject *application = get(model, 0, "application").value(); QCOMPARE(application->metaObject()->className(), "OnlineAccounts::Application"); /* Reset the model to an invalid service */ qmlModel->setProperty("service", QString()); QCOMPARE(model->rowCount(), 0); delete qmlModel; } QTEST_MAIN(PluginTest); #include "tst_plugin.moc" ./tests/mock/0000755000004100000410000000000013071130602013327 5ustar www-datawww-data./tests/mock/signon.h0000644000004100000410000002231413071130602014777 0ustar www-datawww-data/* * Copyright (C) 2013 Canonical Ltd. * * Contact: Alberto Mardegan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef MOCK_SIGNON_H #define MOCK_SIGNON_H #include #include #include #include namespace SignOn { class AuthSession; typedef QPointer AuthSessionP; class Error { public: enum ErrorType { Unknown = 1, /**< Catch-all for errors not distinguished by another code. */ InternalServer = 2, /**< Signon Daemon internal error. */ InternalCommunication = 3, /**< Communication with Signon Daemon error. */ PermissionDenied = 4, /**< The operation cannot be performed due to insufficient client permissions. */ EncryptionFailure, /**< Failure during data encryption/decryption. */ AuthServiceErr = 100, /* Placeholder to rearrange enumeration - AuthService specific */ MethodNotKnown, /**< The method with this name is not found. */ ServiceNotAvailable, /**< The service is temporarily unavailable. */ InvalidQuery, /**< Parameters for the query are invalid. */ IdentityErr = 200, /* Placeholder to rearrange enumeration - Identity specific */ MethodNotAvailable, /**< The requested method is not available. */ IdentityNotFound, /**< The identity matching this Identity object was not found on the service. */ StoreFailed, /**< Storing credentials failed. */ RemoveFailed, /**< Removing credentials failed. */ SignOutFailed, /**< SignOut failed. */ IdentityOperationCanceled, /**< Identity operation was canceled by user. */ CredentialsNotAvailable, /**< Query failed. */ ReferenceNotFound, /**< Trying to remove nonexistent reference. */ AuthSessionErr = 300, /* Placeholder to rearrange enumeration - AuthSession/AuthPluginInterface specific */ MechanismNotAvailable, /**< The requested mechanism is not available. */ MissingData, /**< The SessionData object does not contain necessary information. */ InvalidCredentials, /**< The supplied credentials are invalid for the mechanism implementation. */ NotAuthorized, /**< Authorization failed. */ WrongState, /**< An operation method has been called in a wrong state. */ OperationNotSupported, /**< The operation is not supported by the mechanism implementation. */ NoConnection, /**< No Network connetion. */ Network, /**< Network connetion failed. */ Ssl, /**< Ssl connection failed. */ Runtime, /**< Casting SessionData into subclass failed */ SessionCanceled, /**< Challenge was cancelled. */ TimedOut, /**< Challenge was timed out. */ UserInteraction, /**< User interaction dialog failed */ OperationFailed, /**< Temporary failure in authentication. */ EncryptionFailed, /**< @deprecated Failure during data encryption/decryption. */ TOSNotAccepted, /**< User declined Terms of Service. */ ForgotPassword, /**< User requested reset password sequence. */ MethodOrMechanismNotAllowed, /**< Method or mechanism not allowed for this identity. */ IncorrectDate, /**< Date time incorrect on device. */ UserErr = 400 /* Placeholder to rearrange enumeration - User space specific */ }; Error() : m_type((int)Unknown), m_message(QString()) {} Error(const Error &src) : m_type(src.type()), m_message(src.message()) {} Error(int type, const QString &message = QString()): m_type(type), m_message(message) {} Error &operator=(const Error &src) { m_type = src.type(); m_message = src.message(); return *this; } virtual ~Error() {} void setType(int type) { m_type = type; } void setMessage(const QString &message) { m_message = message; } int type() const { return m_type; } QString message() const { return m_message; } private: int m_type; QString m_message; }; class SessionData { public: SessionData(const QVariantMap &data = QVariantMap()) { m_data = data; } SessionData(const SessionData &other) { m_data = other.m_data; } SessionData &operator=(const SessionData &other) { m_data = other.m_data; return *this; } QVariantMap toMap() const { return m_data; } protected: QVariantMap m_data; }; typedef QString MethodName; typedef QStringList MechanismsList; class IdentityInfoImpl; class IdentityInfo { public: enum CredentialsType { Other = 0, Application = 1 << 0, Web = 1 << 1, Network = 1 << 2 }; public: IdentityInfo(); IdentityInfo(const IdentityInfo &other); IdentityInfo &operator=(const IdentityInfo &other); ~IdentityInfo(); void setId(const quint32 id); quint32 id() const; void setSecret(const QString &secret, const bool storeSecret = true); QString secret() const; bool isStoringSecret() const; void setStoreSecret(const bool storeSecret); void setUserName(const QString &userName); const QString userName() const; void setCaption(const QString &caption); const QString caption() const; void setRealms(const QStringList &realms); QStringList realms() const; void setOwner(const QString &ownerToken); QString owner() const; void setAccessControlList(const QStringList &accessControlList); QStringList accessControlList() const; void setMethod(const MethodName &method, const MechanismsList &mechanismsList); void removeMethod(const MethodName &method); void setType(CredentialsType type); CredentialsType type() const; QList methods() const; MechanismsList mechanisms(const MethodName &method) const; private: class IdentityInfoImpl *impl; }; class Identity: public QObject { Q_OBJECT Q_DISABLE_COPY(Identity) protected: Identity(const quint32 id = 0, QObject *parent = 0); public: static Identity *newIdentity(const IdentityInfo &info = IdentityInfo(), QObject *parent = 0); static Identity *existingIdentity(const quint32 id, QObject *parent = 0); virtual ~Identity(); AuthSessionP createSession(const QString &methodName); void storeCredentials(const IdentityInfo &info = IdentityInfo()); void remove(); void queryInfo(); Q_SIGNALS: void error(const SignOn::Error &err); void credentialsStored(const quint32 id); void info(const SignOn::IdentityInfo &info); void removed(); private Q_SLOTS: void emitCredentialsStored(); void emitInfo(); void emitRemoved(); private: static quint32 lastId; quint32 m_id; IdentityInfo m_info; QTimer m_storeTimer; QTimer m_infoTimer; QTimer m_removeTimer; }; class AuthSession: public QObject { Q_OBJECT friend class Identity; protected: AuthSession(quint32 id, const QString &methodName, QObject *parent = 0); ~AuthSession(); public: void process(const SessionData &sessionData, const QString &mechanism = QString()); void cancel(); Q_SIGNALS: void error(const SignOn::Error &err); void response(const SignOn::SessionData &sessionData); private Q_SLOTS: void respond(); private: quint32 m_id; QString m_method; QString m_mechanism; QVariantMap m_sessionData; QTimer responseTimer; }; }; // namespace Q_DECLARE_METATYPE(SignOn::Error) Q_DECLARE_METATYPE(SignOn::IdentityInfo) Q_DECLARE_METATYPE(SignOn::SessionData) #endif // MOCK_SIGNON_H ./tests/mock/signon.cpp0000644000004100000410000001566313071130602015343 0ustar www-datawww-data/* * Copyright (C) 2013 Canonical Ltd. * * Contact: Alberto Mardegan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "signon.h" #include #include using namespace SignOn; namespace SignOn { class IdentityInfoImpl { private: friend class IdentityInfo; QVariantMap fields; }; QHash storedIdentities; }; // namespace IdentityInfo::IdentityInfo(): impl(new IdentityInfoImpl) { } IdentityInfo::IdentityInfo(const IdentityInfo &other): impl(new IdentityInfoImpl) { impl->fields = other.impl->fields; } IdentityInfo &IdentityInfo::operator=(const IdentityInfo &other) { impl->fields = other.impl->fields; return *this; } IdentityInfo::~IdentityInfo() { delete impl; } void IdentityInfo::setId(const quint32 id) { impl->fields["id"] = id; } quint32 IdentityInfo::id() const { return impl->fields.value("id", 0).toUInt(); } void IdentityInfo::setSecret(const QString &secret, const bool storeSecret) { impl->fields["secret"] = secret; setStoreSecret(storeSecret); } QString IdentityInfo::secret() const { return impl->fields.value("secret").toString(); } bool IdentityInfo::isStoringSecret() const { return impl->fields.value("storeSecret").toBool(); } void IdentityInfo::setStoreSecret(const bool storeSecret) { impl->fields["storeSecret"] = storeSecret; } void IdentityInfo::setUserName(const QString &userName) { impl->fields["userName"] = userName; } const QString IdentityInfo::userName() const { return impl->fields.value("userName").toString(); } void IdentityInfo::setCaption(const QString &caption) { impl->fields["caption"] = caption; } const QString IdentityInfo::caption() const { return impl->fields.value("caption").toString(); } void IdentityInfo::setRealms(const QStringList &realms) { impl->fields["realms"] = realms; } QStringList IdentityInfo::realms() const { return impl->fields.value("realms").toStringList(); } void IdentityInfo::setOwner(const QString &ownerToken) { impl->fields["owner"] = ownerToken; } QString IdentityInfo::owner() const { return impl->fields.value("owner").toString(); } void IdentityInfo::setAccessControlList(const QStringList &accessControlList) { impl->fields["accessControlList"] = accessControlList; } QStringList IdentityInfo::accessControlList() const { return impl->fields.value("accessControlList").toStringList(); } void IdentityInfo::setMethod(const MethodName &method, const MechanismsList &mechanismsList) { QVariantMap methods = impl->fields["methods"].toMap(); methods[method] = mechanismsList; impl->fields["methods"] = methods; } void IdentityInfo::removeMethod(const MethodName &method) { QVariantMap methods = impl->fields["methods"].toMap(); methods.remove(method); impl->fields["methods"] = methods; } QList IdentityInfo::methods() const { QVariantMap methods = impl->fields["methods"].toMap(); return methods.keys(); } MechanismsList IdentityInfo::mechanisms(const MethodName &method) const { QVariantMap methods = impl->fields["methods"].toMap(); return methods[method].toStringList(); } void IdentityInfo::setType(CredentialsType type) { impl->fields["type"] = type; } IdentityInfo::CredentialsType IdentityInfo::type() const { return CredentialsType(impl->fields["type"].toInt()); } quint32 Identity::lastId = 1; Identity::Identity(const quint32 id, QObject *parent): QObject(parent), m_id(id) { m_storeTimer.setSingleShot(true); m_storeTimer.setInterval(50); QObject::connect(&m_storeTimer, SIGNAL(timeout()), this, SLOT(emitCredentialsStored())); m_infoTimer.setSingleShot(true); m_infoTimer.setInterval(20); QObject::connect(&m_infoTimer, SIGNAL(timeout()), this, SLOT(emitInfo())); m_removeTimer.setSingleShot(true); m_removeTimer.setInterval(40); QObject::connect(&m_removeTimer, SIGNAL(timeout()), this, SLOT(emitRemoved())); } Identity::~Identity() { } Identity *Identity::newIdentity(const IdentityInfo &info, QObject *parent) { Identity *identity = new Identity(0, parent); identity->m_info = info; return identity; } Identity *Identity::existingIdentity(const quint32 id, QObject *parent) { if (id != 0 && !storedIdentities.contains(id)) return 0; Identity *identity = new Identity(id, parent); identity->m_info = storedIdentities.value(id); return identity; } AuthSessionP Identity::createSession(const QString &methodName) { return new AuthSession(m_id, methodName, this); } void Identity::storeCredentials(const IdentityInfo &info) { if (m_id == 0) { m_id = lastId++; } m_info = info; m_info.setId(m_id); storedIdentities.insert(m_id, m_info); m_storeTimer.start(); } void Identity::remove() { m_removeTimer.start(); } void Identity::queryInfo() { m_infoTimer.start(); } void Identity::emitCredentialsStored() { Q_EMIT credentialsStored(m_id); } void Identity::emitInfo() { Q_EMIT info(m_info); } void Identity::emitRemoved() { if (m_id != 0 && !storedIdentities.contains(m_id)) { Q_EMIT error(Error(Error::RemoveFailed, "Identity was not stored")); return; } storedIdentities.remove(m_id); Q_EMIT removed(); } AuthSession::AuthSession(quint32 id, const QString &methodName, QObject *parent): QObject(parent), m_id(id), m_method(methodName) { responseTimer.setSingleShot(true); responseTimer.setInterval(10); QObject::connect(&responseTimer, SIGNAL(timeout()), this, SLOT(respond())); } AuthSession::~AuthSession() { } void AuthSession::process(const SessionData &sessionData, const QString &mechanism) { m_mechanism = mechanism; m_sessionData = sessionData.toMap(); m_sessionData.insert("credentialsId", m_id); responseTimer.start(); } void AuthSession::cancel() { m_sessionData.insert("errorCode", Error::SessionCanceled); m_sessionData.insert("errorMessage", QStringLiteral("Session canceled")); } void AuthSession::respond() { if (m_sessionData.contains("errorCode")) { Error err(m_sessionData["errorCode"].toInt(), m_sessionData["errorMessage"].toString()); Q_EMIT error(err); } else { Q_EMIT response(m_sessionData); } } ./tests/mock/mock.pro0000644000004100000410000000036313071130602015004 0ustar www-datawww-datainclude(../../common-project-config.pri) TARGET = signon-qt5 TEMPLATE = lib CONFIG += \ debug QT += \ core # Error on undefined symbols QMAKE_LFLAGS += $$QMAKE_LFLAGS_NOUNDEF SOURCES += \ signon.cpp HEADERS += \ signon.h ./tests/tst_plugin.pro0000644000004100000410000000113513071130602015310 0ustar www-datawww-datainclude(../common-project-config.pri) include($${TOP_SRC_DIR}/common-vars.pri) include($${TOP_SRC_DIR}/common-installs-config.pri) TARGET = tst_plugin CONFIG += \ debug \ link_pkgconfig QT += \ core \ qml \ testlib PKGCONFIG += \ accounts-qt5 SOURCES += \ tst_plugin.cpp INCLUDEPATH += \ $$TOP_SRC_DIR/src DATA_PATH = $${TOP_SRC_DIR}/tests/data/ DEFINES += \ TEST_DATA_DIR=\\\"$$DATA_PATH\\\" check.commands = "LD_LIBRARY_PATH=mock:${LD_LIBRARY_PATH} xvfb-run -a dbus-test-runner -m 120 -t ./$${TARGET}" check.depends = $${TARGET} QMAKE_EXTRA_TARGETS += check ./tests/tests.pro0000644000004100000410000000011713071130602014261 0ustar www-datawww-dataTEMPLATE = subdirs CONFIG += ordered SUBDIRS = \ mock \ tst_plugin.pro ./tests/data/0000755000004100000410000000000013071130602013307 5ustar www-datawww-data./tests/data/coolshare.service0000644000004100000410000000027513071130602016654 0ustar www-datawww-data sharing Cool Share general_otherservice cool ./tests/data/e-mail.service-type0000644000004100000410000000027413071130602017017 0ustar www-datawww-data translation_file Electronic mail email_icon ./tests/data/cool.provider0000644000004100000410000000033113071130602016014 0ustar www-datawww-data Cool provider general_myprovider true somewhere ./tests/data/coolmail.service0000644000004100000410000000111713071130602016470 0ustar www-datawww-data e-mail Cool Mail general_myservice cool here ./tests/data/sharing.service-type0000644000004100000410000000027413071130602017306 0ustar www-datawww-data translation_file File sharing sharing_icon ./tests/data/badshare.service0000644000004100000410000000027213071130602016443 0ustar www-datawww-data sharing Bad Share general_otherservice bad ./tests/data/mailer.application0000644000004100000410000000057313071130602017012 0ustar www-datawww-data Mailer application mailer-catalog mailer.desktop Mailer can retrieve your e-mails ./tests/data/badmail.service0000644000004100000410000000026413071130602016264 0ustar www-datawww-data e-mail Bad Mail general_myservice bad ./tests/data/bad.provider0000644000004100000410000000020613071130602015607 0ustar www-datawww-data Bad provider general_myprovider ./tests/data/coolpublisher.application0000644000004100000410000000050113071130602020402 0ustar www-datawww-data Cool publisher mailer-catalog Publish stuff into your Cool account ./tests/data/applications/0000755000004100000410000000000013071130602015775 5ustar www-datawww-data./tests/data/applications/mailer.desktop0000644000004100000410000000024313071130602020640 0ustar www-datawww-data[Desktop Entry] Name=Easy Mailer Comment=Send and receive mail Exec=/bin/sh Icon=mailer-icon Terminal=false Type=Application Categories=Application;Network;Email; ./accounts-qml-module.pro0000644000004100000410000000061413071130602015650 0ustar www-datawww-datainclude(common-vars.pri) include(common-project-config.pri) include(doc/doc.pri) TEMPLATE = subdirs CONFIG += ordered SUBDIRS = \ src \ tests include(common-installs-config.pri) DISTNAME = $${PROJECT_NAME}-$${PROJECT_VERSION} dist.commands = "git archive --format=tar --prefix=$${DISTNAME}/ HEAD | bzip2 -9 > $${DISTNAME}.tar.bz2" dist.depends = distclean QMAKE_EXTRA_TARGETS += dist ./coverage.pri0000644000004100000410000000355513071130602013553 0ustar www-datawww-data# Coverage CONFIG(coverage) { OBJECTS_DIR = MOC_DIR = TOP_SRC_DIR = $$PWD LIBS += -lgcov QMAKE_CXXFLAGS += --coverage QMAKE_LDFLAGS += --coverage QMAKE_EXTRA_TARGETS += coverage cov QMAKE_EXTRA_TARGETS += clean-gcno clean-gcda coverage-html \ generate-coverage-html clean-coverage-html coverage-gcovr \ generate-gcovr generate-coverage-gcovr clean-coverage-gcovr clean-gcno.commands = \ "@echo Removing old coverage instrumentation"; \ "find -name '*.gcno' -print | xargs -r rm" clean-gcda.commands = \ "@echo Removing old coverage results"; \ "find -name '*.gcda' -print | xargs -r rm" coverage-html.depends = clean-gcda check generate-coverage-html generate-coverage-html.commands = \ "@echo Collecting coverage data"; \ "lcov --directory $${TOP_SRC_DIR} --capture --output-file coverage.info --no-checksum --compat-libtool"; \ "lcov --extract coverage.info \"*/src/*.cpp\" -o coverage.info"; \ "lcov --remove coverage.info \"moc_*.cpp\" -o coverage.info"; \ "LANG=C genhtml --prefix $${TOP_SRC_DIR} --output-directory coverage-html --title \"Code Coverage\" --legend --show-details coverage.info" clean-coverage-html.depends = clean-gcda clean-coverage-html.commands = \ "lcov --directory $${TOP_SRC_DIR} -z"; \ "rm -rf coverage.info coverage-html" coverage-gcovr.depends = clean-gcda check generate-coverage-gcovr generate-coverage-gcovr.commands = \ "@echo Generating coverage GCOVR report"; \ "gcovr -x -r $${TOP_SRC_DIR} -o $${TOP_SRC_DIR}/coverage.xml -e \".*/moc_.*\" -e \"tests/.*\" -e \".*\\.h\"" clean-coverage-gcovr.depends = clean-gcda clean-coverage-gcovr.commands = \ "rm -rf $${TOP_SRC_DIR}/coverage.xml" QMAKE_CLEAN += *.gcda *.gcno coverage.info coverage.xml } ./common-installs-config.pri0000644000004100000410000000420213071130602016330 0ustar www-datawww-data#----------------------------------------------------------------------------- # Common installation configuration for all projects. #----------------------------------------------------------------------------- #----------------------------------------------------------------------------- # setup the installation prefix #----------------------------------------------------------------------------- INSTALL_PREFIX = /usr # default installation prefix # default prefix can be overriden by defining PREFIX when running qmake isEmpty( PREFIX ) { message("====") message("==== NOTE: To override the installation path run: `qmake PREFIX=/custom/path'") message("==== (current installation path is `$${INSTALL_PREFIX}')") } else { INSTALL_PREFIX = $${PREFIX} message("====") message("==== install prefix set to `$${INSTALL_PREFIX}'") } #----------------------------------------------------------------------------- # default installation target for applications #----------------------------------------------------------------------------- contains( TEMPLATE, app ) { target.path = $${INSTALL_PREFIX}/bin INSTALLS += target message("====") message("==== INSTALLS += target") } #----------------------------------------------------------------------------- # default installation target for libraries #----------------------------------------------------------------------------- contains( TEMPLATE, lib ) { target.path = $${INSTALL_PREFIX}/lib INSTALLS += target message("====") message("==== INSTALLS += target") # reset the .pc file's `prefix' variable #include( tools/fix-pc-prefix.pri ) } #----------------------------------------------------------------------------- # target for header files #----------------------------------------------------------------------------- !isEmpty( headers.files ) { headers.path = $${INSTALL_PREFIX}/include/$${TARGET} INSTALLS += headers message("====") message("==== INSTALLS += headers") } else { message("====") message("==== NOTE: Remember to add your API headers into `headers.files' for installation!") } # End of File ./examples/0000755000004100000410000000000013071130602013052 5ustar www-datawww-data./examples/ubuntu-sdk-view.qml0000644000004100000410000000527213071130602016644 0ustar www-datawww-data/* * Copyright 2013 Canonical Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.0 import Ubuntu.Components 0.1 import Ubuntu.Components.ListItems 0.1 as ListItem import Ubuntu.OnlineAccounts 0.1 MainView { width: units.gu(60) height: units.gu(80) Behavior on x { SequentialAnimation{ PropertyAction { property: "x"; value: main.width } PropertyAnimation { duration: 200 } } } AccountServiceModel { id: accounts serviceType: "microblogging" //serviceType: "IM" includeDisabled: true Component.onCompleted: { set_model()} } function set_model () { listView.model = accounts; console.log ("MODEL READY: " + listView.count); } ListView { id: listView width: parent.width height: parent.height anchors.fill: parent focus: true delegate: Item { width: parent.width height: childrenRect.height AccountService { id: accts objectHandle: accountServiceHandle onAuthenticated: { console.log("Access token is " + reply.AccessToken) } onAuthenticationError: { console.log("Authentication failed, code " + error.code) } Component.onCompleted: { sw.checked = accts.enabled; } onEnabledChanged: { console.log ("ENABLED CHANGED"); sw.checked = accts.enabled; } } ListItem.Standard { text: displayName icon: "image://gicon/"+accts.provider.iconName control { Switch { id: sw checked: false } } onClicked: accts.authenticate(null) } } populate: Transition { NumberAnimation { properties: "x,y"; duration: 1000 } } } } ./examples/simple-view.qml0000644000004100000410000000446113071130602016033 0ustar www-datawww-data/* * Copyright 2013 Canonical Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.0 import Ubuntu.OnlineAccounts 0.1 Rectangle { width: 400 height: 300 AccountServiceModel { id: accounts serviceType: "microblogging" includeDisabled: true } ListView { id: listView width: parent.width height: parent.height anchors.fill: parent focus: true model: accounts spacing: 3 delegate: Item { width: parent.width height: 60 AccountService { id: accts objectHandle: accountServiceHandle onAuthenticated: { console.log("Access token is " + reply.AccessToken) } onAuthenticationError: { console.log("Authentication failed, code " + error.code) } onEnabledChanged: { console.log ("ENABLED CHANGED"); } } Rectangle { anchors.fill: parent radius: 10 color: accts.enabled ? "lightsteelblue" : "#777" Column { anchors.fill: parent anchors.margins: 5 Text { font.bold: true text: providerName } Text { text: displayName } } MouseArea { anchors.fill: parent onClicked: accts.authenticate(null) } } } } } ./examples/create-account.qml0000644000004100000410000000443413071130602016467 0ustar www-datawww-data/* * Copyright 2013 Canonical Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ import QtQuick 2.0 import Ubuntu.OnlineAccounts 0.1 Rectangle { width: 400 height: 300 ListView { id: listView anchors.fill: parent focus: true model: ProviderModel {} spacing: 3 delegate: Item { width: parent.width height: 60 Account { id: account objectHandle: Manager.createAccount(model.providerId) } AccountService { id: globalAccountSettings objectHandle: account.accountServiceHandle credentials: Credentials { id: creds userName: "my name" secret: "password you'll never guess" caption: account.provider.id acl: ["*"] } } Rectangle { anchors.fill: parent radius: 10 color: "lightsteelblue" Text { font.bold: true text: model.displayName } MouseArea { anchors.fill: parent /* Uncomment this to test the account creation. WARNING: this will create a real account on your system, with dummy data! */ // onClicked: writeAccount() } } function writeAccount() { account.updateDisplayName("Delete me"); account.updateEnabled(false); creds.sync(); } } } } ./doc/0000755000004100000410000000000013071130602012001 5ustar www-datawww-data./doc/ubuntu-appdev-site-header.qdocconf0000644000004100000410000000340013071130602020503 0ustar www-datawww-dataHTML.postheader = \ "
\n" \ "
\n" \ " \n" \ "
\n" \ "
\n" \ "
\n" \ " \n" \ " \"Ubuntu\n" \ "

App Developer

\n" \ "
\n" \ "
\n" \ " \n" \ "
\n" \ "
\n" \ "
\n" ./doc/manifest-files.qdoc0000644000004100000410000003143313071130602015563 0ustar www-datawww-data/* * Copyright (C) 2012 Canonical Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 3. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ /*! \page manifest-files.html manifest-files \title Manifest files In order to integrate with the Online Accounts framework, one needs to ship a couple of manifest files which describe the online services being used or provided. Account plugins must ship a \l{The provider files}{provider file} which can contain account settings readable by applications. Applications must ship an \l{The application files}{application file} which tells which online services the application is able to use, and \l{The service files}{service files} which describes the online services and their settings. \section1 The provider files A \c .provider file describes an online accounts provider. It's a XML file, typically installed in \c /usr/share/accounts/providers/ or \c ~/.local/share/accounts/providers/ which looks like this: \code Facebook facebook account-plugins .*facebook\.com generic-oauth true \endcode This file name must match the value of the \c id tag in the root \c element, plus the \c ".provider" suffix. The only mandatory element is \c , and that's the display name of the provider. Other optional elements are: \list \li \c : an icon for the account provider. \li \c : a translation domain for the \c element. \li \c : a regular expression matching the domain(s) where this account is used. \li \c : the ID of the account plugin which must be used to create or edit the accounts for this provider. \li \c : whether the account editing UI should prevent the user to create multiple accounts for this provider. \li \c