From 7c95aedfd369f1fabc3a1195c51229ea4874c5b9 Mon Sep 17 00:00:00 2001 From: Vikas Pachdha Date: Fri, 3 Mar 2017 16:20:53 +0100 Subject: [PATCH] iOS: Add API's to get provisioning data Change-Id: I927b2dbaa9e6c175d90b1407418570bbd2a3d96e Reviewed-by: Eike Ziller --- src/plugins/ios/iosconfigurations.cpp | 217 +++++++++++++++++++++++++- src/plugins/ios/iosconfigurations.h | 70 ++++++++- 2 files changed, 281 insertions(+), 6 deletions(-) diff --git a/src/plugins/ios/iosconfigurations.cpp b/src/plugins/ios/iosconfigurations.cpp index 19f6f39d63b..4a6e004da1d 100644 --- a/src/plugins/ios/iosconfigurations.cpp +++ b/src/plugins/ios/iosconfigurations.cpp @@ -32,6 +32,7 @@ #include #include +#include #include #include #include @@ -49,12 +50,15 @@ #include #include +#include #include #include +#include #include #include +#include +#include #include -#include #include using namespace ProjectExplorer; @@ -74,6 +78,21 @@ namespace Internal { const QLatin1String SettingsGroup("IosConfigurations"); const QLatin1String ignoreAllDevicesKey("IgnoreAllDevices"); +const char provisioningTeamsTag[] = "IDEProvisioningTeams"; +const char freeTeamTag[] = "isFreeProvisioningTeam"; +const char emailTag[] = "eMail"; +const char teamNameTag[] = "teamName"; +const char teamIdTag[] = "teamID"; + +const char udidTag[] = "UUID"; +const char profileNameTag[] = "Name"; +const char appIdTag[] = "AppIDName"; +const char expirationDateTag[] = "ExpirationDate"; +const char profileTeamIdTag[] = "TeamIdentifier"; + +static const QString xcodePlistPath = QDir::homePath() + "/Library/Preferences/com.apple.dt.Xcode.plist"; +static const QString provisioningProfileDirPath = QDir::homePath() + "/Library/MobileDevice/Provisioning Profiles"; + static Core::Id deviceId(const Platform &platform) { if (platform.name.startsWith(QLatin1String("iphoneos-"))) @@ -246,6 +265,20 @@ static QVersionNumber findXcodeVersion() return QVersionNumber(); } +static QByteArray decodeProvisioningProfile(const QString &path) +{ + QTC_ASSERT(!path.isEmpty(), return QByteArray()); + + Utils::SynchronousProcess p; + p.setTimeoutS(3); + // path is assumed to be valid file path to .mobileprovision + const QStringList args = {"smime", "-inform", "der", "-verify", "-in", path}; + Utils::SynchronousProcessResponse res = p.runBlocking("openssl", args); + if (res.result != Utils::SynchronousProcessResponse::Finished) + qCDebug(iosCommonLog) << "Reading signed provisioning file failed" << path; + return res.stdOut().toLatin1(); +} + void IosConfigurations::updateAutomaticKitList() { const QList platforms = handledPlatforms(); @@ -320,9 +353,9 @@ void IosConfigurations::updateAutomaticKitList() KitManager::deregisterKit(kit); } -static IosConfigurations *m_instance = 0; +static IosConfigurations *m_instance = nullptr; -QObject *IosConfigurations::instance() +IosConfigurations *IosConfigurations::instance() { return m_instance; } @@ -409,6 +442,137 @@ void IosConfigurations::setDeveloperPath(const FileName &devPath) } } +void IosConfigurations::initializeProvisioningData() +{ + // Initialize provisioning data only on demand. i.e. when first call to a provisioing data API + // is made. + static bool initialized = false; + if (initialized) + return; + initialized = true; + + m_instance->loadProvisioningData(false); + + // Watch the provisioing profiles folder and xcode plist for changes and + // update the content accordingly. + m_provisioningDataWatcher = new QFileSystemWatcher(this); + m_provisioningDataWatcher->addPath(xcodePlistPath); + m_provisioningDataWatcher->addPath(provisioningProfileDirPath); + connect(m_provisioningDataWatcher, &QFileSystemWatcher::directoryChanged, + std::bind(&IosConfigurations::loadProvisioningData, this, true)); + connect(m_provisioningDataWatcher, &QFileSystemWatcher::fileChanged, + std::bind(&IosConfigurations::loadProvisioningData, this, true)); +} + +void IosConfigurations::loadProvisioningData(bool notify) +{ + m_developerTeams.clear(); + m_provisioningProfiles.clear(); + + // Populate Team id's + const QSettings xcodeSettings(xcodePlistPath, QSettings::NativeFormat); + const QVariantMap teamMap = xcodeSettings.value(provisioningTeamsTag).toMap(); + QList teams; + QMapIterator accountiterator(teamMap); + while (accountiterator.hasNext()) { + accountiterator.next(); + QVariantMap teamInfo = accountiterator.value().toMap(); + int provisioningTeamIsFree = teamInfo.value(freeTeamTag).toBool() ? 1 : 0; + teamInfo[freeTeamTag] = provisioningTeamIsFree; + teamInfo[emailTag] = accountiterator.key(); + teams.append(teamInfo); + } + + // Sort team id's to move the free provisioning teams at last of the list. + Utils::sort(teams, [](const QVariantMap &teamInfo1, const QVariantMap &teamInfo2) { + return teamInfo1.value(freeTeamTag).toInt() < teamInfo2.value(freeTeamTag).toInt(); + }); + + foreach (auto teamInfo, teams) { + auto team = std::shared_ptr::make_shared(); + team->m_name = teamInfo.value(teamNameTag).toString(); + team->m_email = teamInfo.value(emailTag).toString(); + team->m_identifier = teamInfo.value(teamIdTag).toString(); + team->m_freeTeam = teamInfo.value(freeTeamTag).toInt() == 1; + m_developerTeams.append(team); + } + + const QDir provisioningProflesDir(provisioningProfileDirPath); + foreach (QFileInfo fileInfo, provisioningProflesDir.entryInfoList({"*.mobileprovision"}, QDir::NoDotAndDotDot | QDir::Files)) { + QDomDocument provisioningDoc; + auto profile = ProvisioningProfilePtr::make_shared();; + QString teamID; + if (provisioningDoc.setContent(decodeProvisioningProfile(fileInfo.absoluteFilePath()))) { + QDomNodeList nodes = provisioningDoc.elementsByTagName("key"); + for (int i = 0;im_identifier = e.nextSiblingElement().text(); + + if (e.text().compare(profileNameTag) == 0) + profile->m_name = e.nextSiblingElement().text(); + + if (e.text().compare(appIdTag) == 0) + profile->m_appID = e.nextSiblingElement().text(); + + if (e.text().compare(expirationDateTag) == 0) + profile->m_expirationDate = QDateTime::fromString(e.nextSiblingElement().text(), + Qt::ISODate).toUTC(); + + if (e.text().compare(profileTeamIdTag) == 0) { + teamID = e.nextSibling().firstChildElement().text(); + auto team = developmentTeam(teamID); + if (team) { + profile->m_team = team; + team->m_profiles.append(profile); + } + } + } + } else { + qCDebug(iosCommonLog) << "Error reading provisoing profile" << fileInfo.absoluteFilePath(); + } + + if (profile->m_team) + m_provisioningProfiles.append(profile); + else + qCDebug(iosCommonLog) << "Skipping profile. No corresponding team found" << profile; + } + + if (notify) + emit provisioningDataChanged(); +} + +const DevelopmentTeams &IosConfigurations::developmentTeams() +{ + QTC_CHECK(m_instance); + m_instance->initializeProvisioningData(); + return m_instance->m_developerTeams; +} + +DevelopmentTeamPtr IosConfigurations::developmentTeam(const QString &teamID) +{ + QTC_CHECK(m_instance); + m_instance->initializeProvisioningData(); + return findOrDefault(m_instance->m_developerTeams, + Utils::equal(&DevelopmentTeam::identifier, teamID)); +} + +const ProvisioningProfiles &IosConfigurations::provisioningProfiles() +{ + QTC_CHECK(m_instance); + m_instance->initializeProvisioningData(); + return m_instance->m_provisioningProfiles; +} + +ProvisioningProfilePtr IosConfigurations::provisioningProfile(const QString &profileID) +{ + QTC_CHECK(m_instance); + m_instance->initializeProvisioningData(); + return Utils::findOrDefault(m_instance->m_provisioningProfiles, + Utils::equal(&ProvisioningProfile::identifier, profileID)); +} + static ClangToolChain *createToolChain(const Platform &platform, Core::Id l) { if (!l.isValid()) @@ -455,5 +619,52 @@ QList IosToolChainFactory::autoDetect(const QList &exi return Utils::transform(toolChains, [](ClangToolChain *tc) -> ToolChain * { return tc; }); } +QString DevelopmentTeam::identifier() const +{ + return m_identifier; +} + +QString DevelopmentTeam::displayName() const +{ + return QString("%1 - %2").arg(m_email).arg(m_name); +} + +QString DevelopmentTeam::details() const +{ + return tr("%1 - Free Provisioning Team : %2") + .arg(m_identifier).arg(m_freeTeam ? tr("Yes") : tr("No")); +} + +QDebug &operator<<(QDebug &stream, DevelopmentTeamPtr team) +{ + QTC_ASSERT(team, return stream); + stream << team->displayName() << team->identifier() << team->isFreeProfile(); + foreach (auto profile, team->m_profiles) + stream << "Profile:" << profile; + return stream; +} + +QString ProvisioningProfile::identifier() const +{ + return m_identifier; +} + +QString ProvisioningProfile::displayName() const +{ + return m_name; +} + +QString ProvisioningProfile::details() const +{ + return tr("Team: %1\nApp ID: %2\nExpiration date: %3").arg(m_team->identifier()).arg(m_appID) + .arg(m_expirationDate.toLocalTime().toString(Qt::SystemLocaleShortDate)); +} + +QDebug &operator<<(QDebug &stream, std::shared_ptr profile) +{ + QTC_ASSERT(profile, return stream); + return stream << profile->displayName() << profile->identifier() << profile->details(); +} + } // namespace Internal } // namespace Ios diff --git a/src/plugins/ios/iosconfigurations.h b/src/plugins/ios/iosconfigurations.h index 7ddbb7e4620..a4e1163b01f 100644 --- a/src/plugins/ios/iosconfigurations.h +++ b/src/plugins/ios/iosconfigurations.h @@ -29,6 +29,7 @@ #include #include +#include #include #include #include @@ -36,11 +37,62 @@ QT_BEGIN_NAMESPACE class QSettings; +class QFileSystemWatcher; QT_END_NAMESPACE namespace Ios { namespace Internal { +class DevelopmentTeam; + +class ProvisioningProfile +{ + Q_DECLARE_TR_FUNCTIONS(ProvisioningProfile) +public: + ProvisioningProfile() {} + std::shared_ptr developmentTeam() { return m_team; } + QString identifier() const; + QString displayName() const; + QString details() const; + const QDateTime &expirationDate() const { return m_expirationDate; } + +private: + std::shared_ptr m_team; + QString m_identifier; + QString m_name; + QString m_appID; + QDateTime m_expirationDate; + friend class IosConfigurations; + friend QDebug &operator<<(QDebug &stream, std::shared_ptr profile); +}; + +using ProvisioningProfilePtr = std::shared_ptr; +using ProvisioningProfiles = QList; + +class DevelopmentTeam +{ + Q_DECLARE_TR_FUNCTIONS(DevelopmentTeam) +public: + DevelopmentTeam() {} + QString identifier() const; + QString displayName() const; + QString details() const; + bool isFreeProfile() const { return m_freeTeam; } + bool hasProvisioningProfile() const { return !m_profiles.isEmpty(); } + +private: + QString m_identifier; + QString m_name; + QString m_email; + bool m_freeTeam = false; + ProvisioningProfiles m_profiles; + friend class IosConfigurations; + friend QDebug &operator<<(QDebug &stream, std::shared_ptr team); +}; + +using DevelopmentTeamPtr = std::shared_ptr; +using DevelopmentTeams = QList; + class IosToolChainFactory : public ProjectExplorer::ToolChainFactory { Q_OBJECT @@ -50,13 +102,12 @@ public: QList autoDetect(const QList &existingToolChains) override; }; - class IosConfigurations : public QObject { Q_OBJECT public: - static QObject *instance(); + static IosConfigurations *instance(); static void initialize(); static bool ignoreAllDevices(); static void setIgnoreAllDevices(bool ignoreDevices); @@ -64,6 +115,13 @@ public: static QVersionNumber xcodeVersion(); static Utils::FileName lldbPath(); static void updateAutomaticKitList(); + static const DevelopmentTeams &developmentTeams(); + static DevelopmentTeamPtr developmentTeam(const QString &teamID); + static const ProvisioningProfiles &provisioningProfiles(); + static ProvisioningProfilePtr provisioningProfile(const QString &profileID); + +signals: + void provisioningDataChanged(); private: IosConfigurations(QObject *parent); @@ -71,11 +129,17 @@ private: void save(); void updateSimulators(); static void setDeveloperPath(const Utils::FileName &devPath); + void initializeProvisioningData(); + void loadProvisioningData(bool notify = true); Utils::FileName m_developerPath; QVersionNumber m_xcodeVersion; bool m_ignoreAllDevices; + QFileSystemWatcher *m_provisioningDataWatcher; + ProvisioningProfiles m_provisioningProfiles; + DevelopmentTeams m_developerTeams; }; - +QDebug &operator<<(QDebug &stream, std::shared_ptr profile); +QDebug &operator<<(QDebug &stream, std::shared_ptr team); } // namespace Internal } // namespace Ios