diff options
-rw-r--r-- | protocols/Teams/res/Resource.rc | 56 | ||||
-rw-r--r-- | protocols/Teams/src/proto.h | 31 | ||||
-rw-r--r-- | protocols/Teams/src/resource.h | 6 | ||||
-rw-r--r-- | protocols/Teams/src/teams_login.cpp | 142 | ||||
-rw-r--r-- | protocols/Teams/src/teams_proto.cpp | 1 |
5 files changed, 220 insertions, 16 deletions
diff --git a/protocols/Teams/res/Resource.rc b/protocols/Teams/res/Resource.rc index 189c8cba6a..5aba8192d5 100644 --- a/protocols/Teams/res/Resource.rc +++ b/protocols/Teams/res/Resource.rc @@ -131,6 +131,62 @@ END ///////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_DEVICECODE DIALOGEX 0, 0, 316, 182 +STYLE DS_SETFONT | DS_SETFOREGROUND | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_SYSMENU +EXSTYLE WS_EX_APPWINDOW +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + DEFPUSHBUTTON "Proceed",IDOK,205,161,50,14 + PUSHBUTTON "Cancel",IDCANCEL,259,161,50,14 + LTEXT "Static",IDC_TEXT,7,7,302,147 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_DEVICECODE, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 309 + TOPMARGIN, 7 + BOTTOMMARGIN, 175 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// AFX_DIALOG_LAYOUT +// + +IDD_DEVICECODE AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// diff --git a/protocols/Teams/src/proto.h b/protocols/Teams/src/proto.h index de6a543bd9..aad0d47b7a 100644 --- a/protocols/Teams/src/proto.h +++ b/protocols/Teams/src/proto.h @@ -1,3 +1,5 @@ +#define TEAMS_CLIENT_ID "8ec6bc83-69c8-4392-8f08-b3c986009232" + enum HostType { HOST_OTHER = 0, HOST_LOGIN = 1, @@ -13,6 +15,26 @@ struct AsyncHttpRequest : public MTHttpRequest<CTeamsProto> class CTeamsProto : public PROTO<CTeamsProto> { friend class COptionsMainDlg; + friend class CDeviceCodeDlg; + + class CTeamsProtoImpl + { + friend class CTeamsProto; + CTeamsProto &m_proto; + + CTimer m_loginPoll; + void OnLoginPoll(CTimer *) + { + m_proto.LoginPoll(); + } + + CTeamsProtoImpl(CTeamsProto &pro) : + m_proto(pro), + m_loginPoll(Miranda_GetSystemWindow(), UINT_PTR(this) + 1) + { + m_loginPoll.OnEvent = Callback(this, &CTeamsProtoImpl::OnLoginPoll); + } + } m_impl; CMStringA GetTenant(); @@ -22,6 +44,7 @@ class CTeamsProto : public PROTO<CTeamsProto> LIST<AsyncHttpRequest> m_requests; MEventHandle m_hRequestQueueEvent; HANDLE m_hRequestQueueThread; + CMStringA m_szAccessToken; void __cdecl WorkerThread(void *); @@ -34,9 +57,17 @@ class CTeamsProto : public PROTO<CTeamsProto> void PushRequest(AsyncHttpRequest *request); // login + CMStringW m_wszUserCode; + CMStringA m_szDeviceCode, m_szDeviceCookie, m_szVerificationUrl; + time_t m_iLoginExpires; + void Login(); + void LoginPoll(); + void LoginError(); void RefreshToken(const char *pszScope, AsyncHttpRequest::MTHttpRequestHandler pFunc); + void OnReceiveDevicePoll(MHttpResponse *response, AsyncHttpRequest *pRequest); + void OnReceiveDeviceToken(MHttpResponse *response, AsyncHttpRequest *pRequest); void OnRefreshAccessToken(MHttpResponse *response, AsyncHttpRequest *pRequest); void OnRefreshSubstrate(MHttpResponse *response, AsyncHttpRequest *pRequest); diff --git a/protocols/Teams/src/resource.h b/protocols/Teams/src/resource.h index c9050aea02..dd0cecf12b 100644 --- a/protocols/Teams/src/resource.h +++ b/protocols/Teams/src/resource.h @@ -5,17 +5,19 @@ #define IDI_TEAMS 100 #define IDD_ACCOUNT_MANAGER 101 #define IDD_OPTIONS_MAIN 102 +#define IDD_DEVICECODE 103 #define IDC_LOGIN 1001 #define IDC_PASSWORD 1002 #define IDC_GROUP 1003 +#define IDC_TEXT 1004 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 128 +#define _APS_NEXT_RESOURCE_VALUE 104 #define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1087 +#define _APS_NEXT_CONTROL_VALUE 1085 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif diff --git a/protocols/Teams/src/teams_login.cpp b/protocols/Teams/src/teams_login.cpp index 9be1df5de8..b2b4be0a92 100644 --- a/protocols/Teams/src/teams_login.cpp +++ b/protocols/Teams/src/teams_login.cpp @@ -17,37 +17,120 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. #include "stdafx.h" +#define TEAMS_OAUTH_RESOURCE "https://api.spaces.skype.com" + CMStringA CTeamsProto::GetTenant() { CMStringA ret(getMStringA("Tenant")); return (ret.IsEmpty()) ? "consumers" : ret; } -void CTeamsProto::Login() +void CTeamsProto::LoginError() { - CMStringA szLogin(getMStringA("Login")), szPassword(getMStringA("Password")); - if (szLogin.IsEmpty() || szPassword.IsEmpty()) { - ProtoBroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, 1001); + ProtoBroadcastAck(0, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, 1001); + SetStatus(ID_STATUS_OFFLINE); + + if (m_iLoginExpires) { + m_impl.m_loginPoll.StopSafe(); + m_iLoginExpires = 0; + } +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CTeamsProto::OnReceiveDevicePoll(MHttpResponse *response, AsyncHttpRequest*) +{ + JsonReply reply(response); + if (!reply) { + if (!strstr(response->body, "\"error\":\"authorization_pending\"")) + LoginError(); return; } +} - // login - int oldStatus = m_iStatus; - m_iStatus = ID_STATUS_CONNECTING; - ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, m_iStatus); +void CTeamsProto::LoginPoll() +{ + if (time(0) >= m_iLoginExpires) { + LoginError(); + return; + } - StartQueue(); + auto *pReq = new AsyncHttpRequest(REQUEST_POST, HOST_LOGIN, "/common/oauth2/token", &CTeamsProto::OnReceiveDevicePoll); + pReq->AddHeader("Cookie", m_szDeviceCookie); + pReq << CHAR_PARAM("client_id", TEAMS_CLIENT_ID) << CHAR_PARAM("grant_type", "urn:ietf:params:oauth:grant-type:device_code") + << CHAR_PARAM("code", m_szDeviceCode); + PushRequest(pReq); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +const wchar_t wszLoginMessage[] = + LPGENW("To login into Teams you need to open '%S' in a browser and select your Teams account there.") L"\r\n\r\n" + LPGENW("Enter the following code then: %s.") L"\r\n\r\n" + LPGENW("Click Proceed to copy that code to clipboard and launch a browser"); + +class CDeviceCodeDlg : public CTeamsDlgBase +{ + bool bSucceeded = false; + +public: + CDeviceCodeDlg(CTeamsProto *ppro) : + CTeamsDlgBase(ppro, IDD_DEVICECODE) + {} + + bool OnInitDialog() override + { + CMStringW wszText(FORMAT, TranslateW(wszLoginMessage), m_proto->m_szVerificationUrl.c_str(), m_proto->m_wszUserCode.c_str()); + SetDlgItemTextW(m_hwnd, IDC_TEXT, wszText); + return true; + } + + bool OnApply() override + { + bSucceeded = true; + Utils_OpenUrl(m_proto->m_szVerificationUrl); + return true; + } - RefreshToken("service::api.fl.teams.microsoft.com::MBI_SSL openid profile offline_access", &CTeamsProto::OnRefreshAccessToken); - // RefreshToken("service::api.fl.spaces.skype.com::MBI_SSL openid profile offline_access", &CTeamsProto::OnRefreshAccessToken); - // RefreshToken("https://substrate.office.com/M365.Access openid profile offline_access", &CTeamsProto::OnRefreshSubstrate); + void OnDestroy() override + { + if (!bSucceeded) + m_proto->LoginError(); + } +}; + +static void CALLBACK LaunchDialog(void *param) +{ + (new CDeviceCodeDlg((CTeamsProto *)param))->Show(); } +void CTeamsProto::OnReceiveDeviceToken(MHttpResponse *response, AsyncHttpRequest*) +{ + JsonReply reply(response); + if (!reply) { + LoginError(); + return; + } + + auto &root = reply.data(); + m_wszUserCode = root["user_code"].as_mstring(); + m_szDeviceCode = root["device_code"].as_mstring(); + m_szVerificationUrl = root["verification_url"].as_mstring(); + m_iLoginExpires = time(0) + root["expires_in"].as_int(); + m_impl.m_loginPoll.StartSafe(root["interval"].as_int() * 1000); + m_szDeviceCookie = response->GetCookies(); + + Utils_ClipboardCopy(MClipUnicode(m_wszUserCode)); + CallFunctionAsync(LaunchDialog, this); +} + +///////////////////////////////////////////////////////////////////////////////////////// + void CTeamsProto::RefreshToken(const char *pszScope, AsyncHttpRequest::MTHttpRequestHandler pFunc) { auto *pReq = new AsyncHttpRequest(REQUEST_POST, HOST_LOGIN, "/" + GetTenant() + "/oauth2/v2.0/token", pFunc); - pReq << CHAR_PARAM("scope", pszScope) << CHAR_PARAM("client_id", "8ec6bc83-69c8-4392-8f08-b3c986009232") - << CHAR_PARAM("grant_type", "refresh_token") << CHAR_PARAM("refresh_token", getMStringA("Password")); + pReq << CHAR_PARAM("scope", pszScope) << CHAR_PARAM("client_id", TEAMS_CLIENT_ID) + << CHAR_PARAM("grant_type", "refresh_token") << CHAR_PARAM("refresh_token", m_szAccessToken); PushRequest(pReq); } @@ -56,3 +139,34 @@ void CTeamsProto::OnRefreshAccessToken(MHttpResponse *response, AsyncHttpRequest void CTeamsProto::OnRefreshSubstrate(MHttpResponse *response, AsyncHttpRequest *pRequest) {} + +///////////////////////////////////////////////////////////////////////////////////////// +// module entry point + +void CTeamsProto::Login() +{ + CMStringA szLogin(getMStringA("Login")), szPassword(getMStringA("Password")); + if (szLogin.IsEmpty() || szPassword.IsEmpty()) { + LoginError(); + return; + } + + // login + int oldStatus = m_iStatus; + m_iStatus = ID_STATUS_CONNECTING; + ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, m_iStatus); + + StartQueue(); + + m_szAccessToken = getMStringA("AccessToken"); + if (m_szAccessToken.IsEmpty()) { + auto *pReq = new AsyncHttpRequest(REQUEST_POST, HOST_LOGIN, "/common/oauth2/devicecode", &CTeamsProto::OnReceiveDeviceToken); + pReq << CHAR_PARAM("client_id", TEAMS_CLIENT_ID) << CHAR_PARAM("resource", TEAMS_CLIENT_ID); + PushRequest(pReq); + } + else { + RefreshToken("service::api.fl.teams.microsoft.com::MBI_SSL openid profile offline_access", &CTeamsProto::OnRefreshAccessToken); + // RefreshToken("service::api.fl.spaces.skype.com::MBI_SSL openid profile offline_access", &CTeamsProto::OnRefreshAccessToken); + // RefreshToken("https://substrate.office.com/M365.Access openid profile offline_access", &CTeamsProto::OnRefreshSubstrate); + } +} diff --git a/protocols/Teams/src/teams_proto.cpp b/protocols/Teams/src/teams_proto.cpp index 0cf9fb0e1c..30a942fd99 100644 --- a/protocols/Teams/src/teams_proto.cpp +++ b/protocols/Teams/src/teams_proto.cpp @@ -2,6 +2,7 @@ CTeamsProto::CTeamsProto(const char *protoName, const wchar_t *userName) : PROTO<CTeamsProto>(protoName, userName), + m_impl(*this), m_requests(10), m_wstrCListGroup(this, "DefaultGroup", L"Teams") { |