-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit f32ad52
Showing
7 changed files
with
281 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
[flake8] | ||
ignore = E203, E266, E501, W503, F403, F401 | ||
max-complexity = 18 | ||
select = B,C,E,F,W,T4,B9 | ||
exclude = | ||
.venv | ||
.svn | ||
CVS | ||
.bzr | ||
.hg | ||
.git | ||
__pycache__ | ||
.tox | ||
.eggs | ||
*.egg |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
.DS_Store | ||
*.pyc | ||
*.pyo | ||
*.egg-info/ | ||
dist/ | ||
build/ | ||
.vscode/ | ||
__pycache__ | ||
.venv/ | ||
poetry.lock | ||
test-report.xml |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2024 Zeke Marffy | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
# `ms-requests-session` | ||
|
||
`ms-requests-session` is a tiny Python library that provides a way to login to your Microsoft account programatically in a `requests` `Session`. It is unknown why you would ever want to use this, but that's for you to determine. I just make the library. | ||
|
||
## Usage | ||
|
||
The usage is incredibly simple and straightforward. Just create a new `MSRequestsSession` object and pass it your creds and a user agent. | ||
|
||
```python | ||
from ms_requests_session import MSRequestsSession | ||
|
||
session = MSRequestsSession("[email protected]", "password", "some user agent") | ||
``` | ||
|
||
The returned object is a subclass of `requests.Session` and has you logged in to your Microsoft account. | ||
|
||
## Known issues | ||
|
||
- Rarely (it seems to occur for Microsoft accounts that have not been accessed for a very long time), a required key may be missing from an API response that is required to be passed to the next. This will prevent you from logging in using `ms-requests-session`. There may be a way further traverse the chain of requests needed to login, but I have found that logging in to that account manually once seems to get it back in shape to be used with `ms-requests-session`. If you figure anything out about combatting this, feel free to submit a PR | ||
- Accounts protected by two-factor auth are not supported, and I have no plans to do so ever | ||
|
||
## Special thanks | ||
|
||
A huge thank you to [@Terrance](https://github.com/Terrance) and his [SkPy](https://github.com/Terrance/SkPy) library. He and his library were able to point me in the correct direction for understanding how tricky values such as `t` work. | ||
|
||
The Chromium Dev Tools are your best friend when it comes to reversing services. Do not sleep on them. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from .api import * | ||
|
||
__version__ = "0.0.0" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
import re | ||
|
||
import requests | ||
from bs4 import BeautifulSoup | ||
|
||
POST_URL_RE = r"""urlPost: ?'([A-Za-z0-9:\?_\-\.&/=]+)""" | ||
PPFT_URL_RE = r"""sFTTag: ?'.*value="(.*?)"/>""" | ||
PPFT_URL_RE_2 = r"""sFT: ?'(.*?)'""" | ||
PASSPORT_TEXT_RE = r"""sRandomBlob: ?'(.*?)'""" | ||
PASSWORD_INCORRECT_TEXT_RE = r"""sErrTxt: ?'Your account or password is incorrect\.""" | ||
REQUIRED_LOGIN_POST_KEYS = {"pprid", "t", "NAP", "ANON"} | ||
LIVE_URL = "https://login.live.com/" | ||
|
||
|
||
class MSRequestsSessionLoginError(Exception): | ||
pass | ||
|
||
|
||
def _regex_search_and_get_first_group(pattern: str, string: str) -> str: | ||
m = re.search(pattern, string) | ||
if m: | ||
return m.group(1) | ||
else: | ||
raise MSRequestsSessionLoginError( | ||
f"Group 1 of {pattern} not found in input string" | ||
) | ||
|
||
|
||
class MSRequestsSession(requests.Session): | ||
def __init__(self, email: str, password: str, user_agent: str) -> None: | ||
"""A `requests.Session` object that logs you into your Microsoft account. | ||
Args: | ||
email (str): Microsoft account email. | ||
password (str): Microsoft account password. | ||
user_agent (str): The user agent to use to login. | ||
""" | ||
super().__init__() | ||
self.headers.update({"User-Agent": user_agent}), | ||
self._login(email, password) | ||
|
||
def _login(self, email: str, password: str) -> None: | ||
next_url = LIVE_URL | ||
r = self.get(next_url) | ||
|
||
# Retrieve the next URL specified by the login page | ||
next_url = _regex_search_and_get_first_group(POST_URL_RE, r.text) | ||
# Get the special PPFT value which is required for the next POST | ||
ppft = _regex_search_and_get_first_group(PPFT_URL_RE, r.text) | ||
# This is a thing for some reason. Attach some substring of the word "Passport" onto the... uh... passport | ||
ppsx = _regex_search_and_get_first_group(PASSPORT_TEXT_RE, r.text) | ||
data = { | ||
"ps": 2, | ||
"psRNGCDefaultType": None, | ||
"psRNGCEntropy": None, | ||
"psRNGCSLK": None, | ||
"canary": None, | ||
"ctx": None, | ||
"hpgrequestid": None, | ||
"PPFT": ppft, | ||
"PPSX": ppsx, | ||
"NewUser": 1, | ||
"FoundMSAs": None, | ||
"fspost": 0, | ||
"i21": 0, | ||
"CookieDisclosure": 0, | ||
"IsFidoSupported": 1, | ||
"isSignupPost": 0, | ||
"isRecoveryAttemptPost": 0, | ||
"i13": "1", | ||
"i18": None, | ||
"login": email, | ||
"loginfmt": email, | ||
"type": 11, | ||
"LoginOptions": 3, | ||
"lrt": None, | ||
"lrtPartition": None, | ||
"hisRegion": None, | ||
"hisScaleUnit": None, | ||
"passwd": password, | ||
} | ||
r = self.post(next_url, data=data, allow_redirects=True) | ||
if re.search(PASSWORD_INCORRECT_TEXT_RE, r.text): | ||
raise MSRequestsSessionLoginError("Incorrect creds") | ||
next_url = _regex_search_and_get_first_group(POST_URL_RE, r.text) | ||
ppft = _regex_search_and_get_first_group(PPFT_URL_RE_2, r.text) | ||
data = { | ||
"LoginOptions": 3, | ||
"type": 28, | ||
"ctx": None, | ||
"hpgrequestid": None, | ||
"PPFT": ppft, | ||
"canary": None, | ||
} | ||
r = self.post(next_url, data=data, allow_redirects=True) | ||
s = BeautifulSoup(r.text, "html.parser") | ||
# POST to get the T value | ||
next_url = s.find(id="fmHF") | ||
if next_url is None: | ||
raise MSRequestsSessionLoginError("fmHF key was missing from response") | ||
next_url = next_url.get("action") | ||
data = {} | ||
# Collect the keys required for the post from the page source | ||
for key in REQUIRED_LOGIN_POST_KEYS: | ||
value = s.find(id=key) | ||
if value is None: | ||
raise MSRequestsSessionLoginError( | ||
f"{key} value missing from response. Possibly try to manually login to your account, and then try again" | ||
) | ||
data[key] = value.get("value") | ||
|
||
# Last POST to get your cookies! | ||
self.post( | ||
next_url, | ||
data=data, | ||
allow_redirects=True, | ||
headers={ | ||
"Origin": LIVE_URL, | ||
"Referer": LIVE_URL, | ||
}, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
[tool.poetry] | ||
name = "ms-requests-session" | ||
version = "0.0.0" | ||
description = "Provides a requests session logged in to your Microsoft account" | ||
authors = ["Zeke Marffy <[email protected]>"] | ||
packages = [{ include = "ms_requests_session" }] | ||
readme = "README.md" | ||
repository = "https://github.com/zmarffy/ms_requests_session" | ||
homepage = "https://github.com/zmarffy/ms_requests_session" | ||
license = "MIT" | ||
|
||
[tool.poetry.dependencies] | ||
python = "^3.8.1" | ||
poetry = "^1.8.3" | ||
requests = "^2.32.3" | ||
beautifulsoup4 = "^4.12.3" | ||
|
||
[tool.poetry.group.dev.dependencies] | ||
black = "^23.3.0" | ||
isort = "^5.12.0" | ||
flake8 = "^6.0.0" | ||
poethepoet = "^0.19.0" | ||
|
||
[tool.poetry.group.test.dependencies] | ||
pytest = "^7.3.1" | ||
|
||
[build-system] | ||
requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning"] | ||
build-backend = "poetry_dynamic_versioning.backend" | ||
|
||
[tool.black] | ||
include = '\.pyi?$' | ||
exclude = ''' | ||
/( | ||
\.git | ||
| \.hg | ||
| \.mypy_cache | ||
| \.tox | ||
| \.venv | ||
| _build | ||
| buck-out | ||
| build | ||
| dist | ||
| _build_files | ||
)/ | ||
''' | ||
|
||
[tool.isort] | ||
profile = "black" | ||
skip = [ | ||
".bzr", | ||
".direnv", | ||
".eggs", | ||
".git", | ||
".hg", | ||
".mypy_cache", | ||
".nox", | ||
".pants.d", | ||
".svn", | ||
".tox", | ||
".venv", | ||
"__pypackages__", | ||
"_build", | ||
"buck-out", | ||
"build", | ||
"dist", | ||
"node_modules", | ||
"venv", | ||
"_build_files", | ||
] | ||
|
||
[tool.poe.tasks] | ||
clean = "rm -rf .pytest_cache dist ./**/__pycache__ test-report.xml" | ||
_black = "black --diff . --check" | ||
_isort = "isort . -c" | ||
check-format = ["_black", "_isort"] | ||
lint = "flake8" | ||
|
||
[tool.poetry-dynamic-versioning] | ||
enable = true | ||
metadata = true | ||
tagged-metadata = true | ||
dirty = true | ||
format-jinja = "{% if distance == 0 %}{{ base }}{% else %}{{ base }}+{{ distance }}.{{ commit }}{% endif %}" |