diff options
| author | George Hazan <george.hazan@gmail.com> | 2025-03-30 13:46:07 +0300 |
|---|---|---|
| committer | George Hazan <george.hazan@gmail.com> | 2025-03-30 13:46:07 +0300 |
| commit | 1f302e539d9120302fe018b4398e65e658605669 (patch) | |
| tree | a009d87ee0778c3c9afedfa98b3c87a56924882d /protocols/Teams/src/teams_login.cpp | |
| parent | 09aee2a73b7f15cadcb48fcc433f30b256742cc6 (diff) | |
Teams: device code auth implementation
Diffstat (limited to 'protocols/Teams/src/teams_login.cpp')
| -rw-r--r-- | protocols/Teams/src/teams_login.cpp | 142 |
1 files changed, 128 insertions, 14 deletions
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); + } +} |
