Маниакальный веблог » OAuthhttps://softwaremaniacs.org/blog/category/oauth/2022-01-09T13:13:40.753060-08:00ManiacИван Сагалаев о программировании и веб-разработкеhttp://softwaremaniacs.org/media/sm_org/style/photo.jpgnfp
2022-01-09T13:13:40.753060-08:00https://softwaremaniacs.org/blog/2021/10/11/nfp/So what happened was, I fed up manually uploading pictures I export from Darktable to my Flickr photo stream using the browser's file picker. So I decided to do something about it. The initial idea was to craft a FUSE file system which would automatically upload new files, but this ...
<p>So what happened was, I fed up manually uploading pictures I export from <a href="https://www.darktable.org/">Darktable</a> to my <a href="https://www.flickr.com/photos/isagalaev/">Flickr photo stream</a> using the browser's file picker. So I decided to do something about it. The initial idea was to craft a FUSE file system which would automatically upload new files, but this turned out to be hard, so I switched to a much simpler solution: a little <a href="https://www.man7.org/linux/man-pages/man7/inotify.7.html">inotify</a> watcher handing over new files to an upload script. I managed to code up a working solution over a weekend!</p>
<p>More interestingly, I made the watcher part — "nfp", for "New File Processor" — as a generic configurable tool which I <a href="https://nest.pijul.com/isagalaev/nfp">published</a>. It was only when I started writing this very blog post that I stumbled upon a standard Linux tool that does it, <a href="https://www.man7.org/linux/man-pages/man1/inotifywait.1.html">inotifywait</a> :-)</p>
<p>Still, I hope there's something to be salvaged from this project. Read on!</p>
<p><a name=more></a></p>
<h2>Darktable</h2>
<p>Darktable is my tool of choice for working with camera RAWs, and I just want to take a moment to share my appreciation for the folks making it. It's a ridiculously advanced, polished photo processor. A real testament to open-source software.</p>
<p>It actually used to have a Flickr export plugin, but it hasn't been working for a while, and got dropped in recent versions. Which is totally fair because it's very much out of scope for a photo editing software. Having a generic solution like <code>nfp</code> makes much more sense because it can connect arbitrary file producers and consumers. It doesn't even have to be about images.</p>
<h2>Rust</h2>
<p>Since "inotify" sounds very "systems" and "core", I immediately took it as an opportunity to play with <a href="https://www.rust-lang.org/">Rust</a> once more. That was the main reason. A nice side effect of it is that it builds into a small self-contained binary which you can bring with you anywhere. As long as it's Linux, anyway :-)</p>
<p>If I had to mention a single gripe with the language during this last foray, that would be implementing an ordered type with the <code>PartialEq</code>/<code>Eq</code>/<code>PartialOrd</code>/<code>Ord</code> trait family. This just feels unnecessarily hard. I still don't get what's the point of having partial variants, and why things couldn't be inferred from each other. Like, even the <a href="https://doc.rust-lang.org/std/cmp/trait.Ord.html#how-can-i-implement-ord">official docs on <code>Ord</code></a> recommend writing a boilerplate for <code>PartialOrd</code> that just calls out to <code>Ord</code>. I'm sure there are Reasons™ for it, but somehow Python can infer <a href="https://docs.python.org/3/library/functools.html#functools.total_ordering">total ordering</a> from just <code>__eq__</code> and <code>__lt__</code>.</p>
<p><a name=debouncing></a></p>
<h2>Debouncing</h2>
<p>After using the tool for a week I noticed that the uploaded photos didn't have any metadata on them. After some digging this turned out to be due to the way Darktable writes exported files: it does it twice for every file. The second write, I assume, is specifically to add metadata to the already fully written JPEG. The problem was, <code>nfp</code> has been snatching the file away immediately after the first write.</p>
<p>The only way I know how to deal with this problem is "<em>debouncing</em>", a term familiar to programmers working with UI and hardware. Which means, adding a short grace period of waiting until a jittery signal stops appearing on the input or the user stops rapidly clicking a button. Or Darktable stops rapidly overwriting a file.</p>
<p>Quick search for a generic debouncer for Rust turned up only specific solutions tied to mpsc channels, or async streams, or hardware sensors. So <a href="https://nest.pijul.com/isagalaev/nfp:main/EPIHKWJDBRZLG.EQAAA">I wrote my own debounce</a>, which is a passive data structure with a couple of methods that doesn't want to know anything about where you get the data and what's the waiting mechanism. It just tracks time and removes duplicates.</p>
<p>I may yet turn it into a full-blown crate, and may be build a unixy-feeling debounce tool along the lines of:</p>
<pre><code>inotifywait -m -e close_write /path | debounce -t 500 | python upload.py
</code></pre>
<p class=note><small><b>Update:</b> this has been <a href="https://softwaremaniacs.org/blog/2021/10/25/debounce/en/">implemented</a>.</small></p>
<p>To do it properly though, I'll have to implement it as a two-threaded process, which will give me an opportunity to play with concurrency in Rust, something I haven't done yet. In <code>nfp</code> I cheated: it waits for new notifications on the same thread that sleeps for debounce timeouts, so it uses an ugly hack of sleeping in short chunks and constantly checking for new events:</p>
<pre><code>loop {
match debouncer.get() {
State::Empty => break,
State::Wait(_) => sleep(Duration::from_millis(50)),
State::Ready(file) => { ... }
}
for event in inotify.read_events(&mut buffer)? {
debouncer.put(...)
}
}
</code></pre>
<h2>Flickr uploader</h2>
<p>The uploader script was a story in itself. Ironically I spent more time trying to make various existing solutions work for me than I did with <code>nfp</code>, but didn't have any luck. So I ended up cobbling together a Python script using <a href="https://stuvel.eu/software/flickrapi/">flickrapi</a>.</p>
<p>The ugly part of all these scripts is <strong>OAuth</strong>. More precisely, its insistence on having to register a client app to get a unique id and secret (apart from the user auth for whoever is going to be using it). It's totally fine for a web service, but in anything distributed to user-owned general-purpose computers it means that a determined user can fish out the client credentials and use them for something else (oh horrors!) I remember dealing with this problem when we worked on an OAuth service for Yandex around 2009, and we didn't come up with a good solution for it. These days I believe client credentials should be optional, akin to the <code>User-Agent</code> header in HTTP, and shouldn't be used for anything outside of coarse statistical data.</p>
<p>Anyway… Since I'm using this script only for myself, I registered it on Flickr, put the credentials in a config and forgot about it :-)</p>
<p>Here's the whole script for posterity:</p>
<pre><code>import argparse
import logging
from pathlib import Path
import toml
import flickrapi
logging.basicConfig(level='INFO')
log = logging.getLogger()
def main():
parser = argparse.ArgumentParser()
parser.add_argument('filename', type=str)
args = parser.parse_args()
filename = Path(args.filename)
config = toml.load(open(Path(__file__).parent / 'flickr.toml'))
token = flickrapi.auth.FlickrAccessToken(**config['user'])
flickr = flickrapi.FlickrAPI(**{**config['app'], 'token': token})
log.info(f'Uploading {filename}...')
title = filename.name.split('.', 1)[0]
# TODO: use 'Xmp.darktable.colorlabels' to control visibility
result = flickr.upload(filename, title=title, is_public=0)
if result.attrib['stat'] != 'ok':
raise RuntimeError(result)
log.info(f'Successfully uploaded {filename}')
if __name__ == '__main__':
main()
</code></pre>
<p>P.S. I love dict destructuring with <code>**</code>!</p>
<h2>What's next</h2>
<p>I'm not yet sure what to do with <code>nfp</code>. The good sense tells me to extract a debouncer out of it and drop the rest in favor of <code>inotifywait</code>, but it actually does add some extra value: it has a sensible config format and I can modify it further into being able to exec multiple processor scripts in parallel. Although I suspect the latter part can be handled by yet another unix voodoo :-)</p>
<p>And its best feature is that it works for me right now!OAuth is not a protocol
2013-01-09T16:07:52.971000-08:00https://softwaremaniacs.org/blog/2012/07/30/oauth-is-not-a-protocol/Though this post is obviously triggered by the recent damnation of OAuth 2.0 by the (former) spec editor Eran Hammer, it's not directly related to it. These are my thoughts about the technical role of OAuth that I wanted to blog about a year ago but couldn't force myself to ...
<p>Though this post is obviously triggered by the recent <a href="http://hueniverse.com/2012/07/oauth-2-0-and-the-road-to-hell/">damnation of OAuth 2.0</a> by the (former) spec editor Eran Hammer, it's not directly related to it. These are my thoughts about the technical role of OAuth that I wanted to blog about a year ago but couldn't force myself to do it. But as of now, it seems surprisingly relevant!</p>
<p>My experience with OAuth comes from being a project manager of the <a href="http://api.yandex.com/oauth/">OAuth server at Yandex</a> where I took part in the design, closely watched the implementation (up to about draft 16 of the spec) and helped writing developer docs.</p>
<p><a name=more></a></p>
<h2>Too much freedom</h2>
<p>What always struck me as odd was the amount of details in the OAuth spec that are implementation dependent. Here are some from the top of my head that I still remember:</p>
<ul>
<li>Endpoint URLs are neither fixed nor is there any discovery mechanism for them.</li>
<li>Application registration is mentioned but doesn't have any properties upon which implementors could rely.</li>
<li>At one point the <code>client_secret</code> wasn't required leaving client authorization basically undefined too.</li>
<li>The authorization scope — both the set of permissions and their semantics — is completely service specific.</li>
<li>There are several ways to define a scope for a client: either during registration of an application or during an authorization request.</li>
<li>There are no guarantees about the rights implied by the access token: nothing prevents a service from limiting the authorization scope of a token based on meta data or, say, user's decision.</li>
</ul>
<p>Leaving those vast undefined grounds actually does make sense because real-world APIs differ immensely and creating a single detailed consistent authorization framework covering all their use cases is at the least impractical, if not impossible.</p>
<h2>Libraries</h2>
<p>All this means that it's impossible to devise such a thing as a "generic OAuth library" that could perform the complete authorization flow against an arbitrary API. As I understand it, current OAuth libraries provide service for encoding language-native data into GET requests and parsing JSON responses. The rest — designing all of the UI and setting up callback URLs — is still application specific. Actually those applications that talk to one particular API don't even need all that library code supporting different real-world flavors of OAuth and would do fine with plain HTTP and JSON libraries. The only thing that a dedicated OAuth library really buys them is the luxury of reading developer-oriented docs instead of a formal spec (which is no fun at all).</p>
<p>By the way, the very fact that so many different OAuths are still floating around in the wild is an indication that developers don't see the point of converging their code to match a single spec since most of said code will remain service-specific anyway.</p>
<h2>Not a protocol?</h2>
<p>The point of any Internet protocol is enabling interoperability between arbitrary parties without additional peer-to-peer agreements. Your HTTP client can (mostly) talk to any HTTP server on the Web without so much as even learning its name. Since it doesn't seem to be the case with OAuth, wouldn't it be better to treat it as a set of best practices instead of a protocol? An "architectural style" if you will…</p>
<p>And may be there should be really two different things:</p>
<ul>
<li>"WebAuth": a set of guidelines with all the different authorization flows and security considerations defined by current OAuth spec,</li>
<li>"OLogin": a protocol with concrete endpoints, common permissions and client identification intended for a use-case of "Login with Something" that seems to be one thing that many different APIs share.</li>
</ul>
<p>Does it make sense to anyone?Разница между OpenID и OAuth
2012-07-30T17:49:51.538000-07:00https://softwaremaniacs.org/blog/2011/07/14/openid-oauth-difference/Как ни странно, путаница в понятиях "OpenID" и "OAuth" встречается довольно часто. Люди используют их как взаимозаменяемые термины или считают OAuth своего рода новой улучшенной версией OpenID. Но на самом деле это совершенно разные вещи. Чтобы было куда ссылаться в таких случаях, пишу пост. Этот пост не о том, что ...
<p>Как ни странно, путаница в понятиях "OpenID" и "OAuth" встречается довольно часто. Люди используют их как взаимозаменяемые термины или считают OAuth своего рода новой улучшенной версией OpenID. Но на самом деле это совершенно разные вещи. Чтобы было куда ссылаться в таких случаях, пишу пост.</p>
<p class=note><small>Этот пост <em>не о том</em>, что из этих двух слов "лучше", что "зло" или что "умерло". Это просто констатация фактов.</small></p>
<p><a name=more></a></p>
<h2>Суть</h2>
<p class="picture right"><img src="/media/blog/openid-logo.png"></p>
<p><strong>OpenID</strong> позволяет сайту удостовериться, что его пользователь владеет неким персональным URL (своим сайтом, блогом, профилем). Этого факта достаточно для того, чтобы использовать уникальный URL для узнавания того же самого пользователя в следующий раз. И всё. Все остальные вещи — заведение аккаунта, получение email'а и других данных, разрешение какой-то активности на сайте — остаётся на усмотрение сайта. Другими словами, OpenID — это чистая <strong>аутентификация</strong>: вы знаете, кто к вам пришёл, но вольны делать с этим знанием всё, что угодно.</p>
<p class="picture right"><img src="/media/blog/oauth-logo.png"></p>
<p><strong>OAuth</strong> позволяет программе (на вебе или локальной) получить от пользователя права на использование какого-то конкретного API. Права обозначаются токеном, свойства которого никак не определены: он может быть одинаковым для разных пользователей, может быть разным для одного в разное время. Всё, что гарантируется — это что программа в обмен на токен сможет выполнять какие-то действия на каком-то сервисе. Другими словами, OAuth — это чистая <strong>авторизация</strong>: вы обладаете конкретными правами, но не можете в общем случае по ним определить, кому они принадлежат.</p>
<p>Аналогия. OpenID — это ваш паспорт: он говорит, кто вы, но что он даёт, зависит от места, куда вы с ним пришли. OAuth — ключи от вашей машины: с ними можно ездить на вашей машине, даже не зная, как вас зовут.</p>
<h2>Вход на сайты</h2>
<p class="picture right"><img src="/media/blog/twitter-facebook.png"></p>
<p>Исходя из написанного выше, естественной технологией для реализации входа на сайты с существующим сторонним аккаунтом является OpenID. Больше того, из-за неопределённости понятия токена "входа через OAuth" вообще не может существовать в природе. Тем не менее, вездесущие входы через Твиттер и Фейсбук используют как раз OAuth. Где неувязка?</p>
<p>И Твиттер, и Фейсбук используют OAuth, как деталь реализации, но в остальном у них нет ничего общего. Роли протоколов выглядят в этом случае так:</p>
<ul>
<li>OAuth предоставляет сайту <strong>авторизацию</strong> на использование API Твиттера/Фейсбука</li>
<li>а вот уже через API можно провести <strong>аутентификацию</strong> ("вход") пользователя, так как оба они предоставляют некую идентифицирующую информацию</li>
</ul>
<p>Аналогия. "Вход через OAuth" — это ключи от машины, в которой по случаю оказались ещё и именные документы владельца.</p>
<p>Важно понимать, что поскольку аутентификацию предоставляет проприетарный API, это не "вход через OAuth", а "вход через Твиттер/Фейсбук". На практике это значит, что нельзя написать некую обобщённую OAuth-библиотеку для входа через любой произвольный сервис типа Твиттера и Фейсбука. </p>
<p>Хотя…</p>
<h2>OpenID Connect</h2>
<p>Идея обобщённого входа через OAuth может работать, если вместо проприетарного API аутентификации стандартизовать какой-нибудь открытый. И такая попытка была — <a href="http://openidconnect.com/">OpenID Connect</a>. Там главная идея OpenID — открытая аутентификация — отделяется от протокола обмена ключами, в качестве которого берётся как раз OAuth. И ещё решаются сопутствующие вопросы типа discovery-механизма, которого в OAuth нет.</p>
<p>Впрочем, на практике OpenID Connect нигде не используется. Почему — совсем отдельная тема.Дизайн API Я.ру
2012-07-30T17:51:48.203000-07:00https://softwaremaniacs.org/blog/2010/08/03/api-yaru-design/Вчера мы открыли в бету API для Я.ру. Это был первый пост в корпоративном блоге Яндекса с кодом на Питоне, что даже породило фан-арт :-). Для меня этот запуск имеет большое эмоциональное значение, потому что машиночитаемый веб — мой давний интерес, и этот проект — первый неигрушечный публичный API, где ...
<p>Вчера мы <a href="http://clubs.ya.ru/company/replies.xml?item_no=27873">открыли в бету</a> API для Я.ру. Это был первый пост в корпоративном блоге Яндекса с кодом на Питоне, что даже породило <a href="http://look4job.ya.ru/replies.xml?item_no=4522">фан-арт</a> :-). Для меня этот запуск имеет большое эмоциональное значение, потому что машиночитаемый веб — мой давний интерес, и этот проект — первый неигрушечный публичный API, где я занимаюсь дизайном, и могу смотреть, как выживают на практике теоретические соображения о том, как это должно делаться.</p>
<p>Я говорю тут от своего лица, и чтобы не возникало ложных ощущений, должен сказать, что я это всё делаю, конечно, не один. Начинал писать собственно серверную часть <a href="http://ijon-c.ya.ru/">Иван Челюбеев</a>. Моя текущая роль — проектировщик и менеджер. Код пишет сейчас <a href="http://kmerenkov.ru/">Костя Меренков</a>, а со стороны Я.рушного бэкенда нам помогает <a href="http://s-chistovich.ya.ru/">Серёжа Чистович</a>.</p>
<p>Этот пост — несколько заметок о том, как всё устроено внутри. Пишите в комментариях, если что-то нужно раскрыть подробнее.</p>
<p><a name=more></a></p>
<h2>Сервис над сервисом</h2>
<p>Сам Я.ру — многоуровневая кодобаза на нескольких языках и технологиях. Серверная её часть общается с внешним для себя миром по CORBA, и поэтому не подходит для публичного API. Кроме того, публичное API поддерживать тяжелее, чем внутреннее, из-за необходимости думать про обратную совместимость. Поэтому API сервиса Я.ру — это по сути ещё один отдельный сервис, который смотрит во внешний мир через HTTP, а внутрь — через CORBA.</p>
<p>Сервис этот написан на <a href="http://www.djangoproject.com/">Джанго</a>. Он довольно нетипичен для джанговского сервиса, потому что не использует моделей (у него вообще нет своих собственных данных), форм и шаблонов. Тем не менее, он сделан на Джанго... для простоты. Эта шокирующая идея идёт вразрез с общим трендом в тусовке модных питонистов, где принято считать Джангу "тяжёлой" (что бы это ни значило), и отказываться от неё сразу же, как только удастся найти хотя бы один компонент, который в проекте не нужен. У нас всё наоборот. Поскольку мы давно привыкли отлаживать, пакетировать, деплоить и мониторить Джанго-проекты, это делать проще, чем уживаться с любым новым фреймворком. И даже в отсутствии "более основных" компонентов сильно помогают знакомые полезные вещи: urlconf, middleware, модель запросов и ответов с вьюхами, дебагные страницы.</p>
<h2>REST</h2>
<p>Учитывая <a href="http://softwaremaniacs.org/blog/2008/11/02/rest-vs-ws/">моё отношение к REST</a>, вряд ли должно быть сюрпризом, что этот API именно такой. Если совсем честно, это было даже постановкой задачи. То есть не было так, как обычно происходит в статьях по проектированию: собрались люди и стали думать, какими средствами решать задачу "сделать API для Я.ру" и выбрали лучшую технологию для задачи. Это скучно! Мы собрались делать модный REST'овый API, а для чего именно — не суть важно :-).</p>
<h3>Ресурсы и операции</h3>
<p>Тем не менее, с какой стороны ни подходи, а я считаю, что API такого рода, как Я.ру, ложится на REST'овую идеологию почти идеально. Все ресурсы получаются совершенно естественно: люди, клубы и посты. Никакой тебе процессоорентированности или транзитивных состояний. </p>
<p>Операции над ресурсами есть очевидные, а есть не очень.</p>
<p>Очевидное — это например изменение данных профиля. Оно происходит как GET ресурса, изменение в XML нужных элементов и PUT всего профиля обратно на то же место. Здесь мы какое-то время думали над тем, чтобы поддержать обработку на входе "частичных представлений": поскольку полный профиль пользователя содержит довольно много полей, возможно клиенту было бы удобней передавать не все поля, а только те, что меняются. Тогда XML'ка могла бы выглядеть буквально как <code><person xmlns="yandex:data"><name>Новое имя</name></person></code>. Но это оказалось хлопотно на связке между API и бэкендом, и мы забросили идею. Поэтому руками составлять XML не надо, его надо считывать и менять.</p>
<p>К неочевидным операциям относятся те, которые в Я.ру делаются по сайд-эффекту создания поста: подружение/раздружение, смена настроения, присоединение к клубам. Например очевидным способом подружиться был бы POST человека (в виде URI или документа) в свою коллекцию друзей, но сейчас это делается POST'ом поста специального типа в свой фид. И хотя прямую операцию можно поддержать технически, создавая при этом пустой пост, этого делать не хочется намеренно. Потому что идеология Я.ру в том, чтобы всё интересное происходило во френдленте. А пустой пост — это не интересно, это спам.</p>
<p class=note><small>Вот прямо сейчас я понял, что это не отражено чётко в нашей документации.</small></p>
<h3>Адресуемость и навигация</h3>
<p>REST диктует, что все ресурсы должны иметь свой URI. А вот что часто упускается, так это что генерацию этих URI должен брать на себя по возможности сервер, а не клиент. То есть когда вы получаете GET'ом представление ресурса, в нём должны быть ссылки, <a href="http://code.google.com/p/uri-templates/">URI-шаблоны</a> и формы, по которым клиент понимает, куда и с чем он может обращаться дальше. На практике с этим есть две проблемы:</p>
<ul>
<li>многих людей эта идея искренне удивляет и даже пугает, все привыкли составлять URL'ы из кусочков;</li>
<li>нормальных библиотек, способных обрабатывать URI templates, мало (под Питон, кажется, <a href="http://code.google.com/p/uri-templates/wiki/Implementations">вообще нет</a>)</li>
</ul>
<p>Однако нам тут повезло, у нас все юзкейсы укладываются просто в обычные URL-ссылки. В итоге, пользуясь ярушным API, клиенту достаточно знать один входной URL: <code>https://api-yaru.yandex.ru/me/</code>, а всё остальное достижимо по возвращаемым ссылкам:</p>
<ul>
<li><code>/me/</code> редиректит на URL текущего залогиненного человека <code>/person/<uid>/</code></li>
<li><code>/person/<uid>/</code> содержит ссылки на посты, список друзей, списки клубов</li>
<li>длинные списки содержат ссылки на следующие страницы</li>
<li>из клубов есть обратные ссылки на своих членов</li>
<li>и т.д.</li>
</ul>
<p>Если вы пользуетесь этим API, и нашли, что вам приходится генерировать ссылку из кусочков — пишите, возможно мы просто упустили какую-то ссылку.</p>
<div class=note>
<p><small>Со ссылками связан один весёлый момент :-).</small></p>
<p><small>Одно время у профилей и клубов был элемент <code><id></code>, который содержал <em>число</em>. Первого же пользователя API это побудило хранить этот id в виде int'а и составлять ссылки с ним вручную. Не зная об этом, мы в какой-то момент элемент оторвали, рассудив, что идентификатор профиля и так уже есть — лежит в <code><link rel="self"></code>. Однако идея использовать длинный URL в качестве идентификатора показалась пользователям слишком радикальной, и в результате короткой битвы мы пришли к компромиссу: id вернули, но в виде короткой строчки :-).</small></p>
<p><small>Мораль в том, что глобально уникальные идентификаторы в REST-системах обязательно будут строчками, чтобы иметь возможность их расширять и комбинировать. К этому надо просто привыкнуть, и спокойно их использовать в качестве ключей.</small></p>
</div>
<h3>Форматы данных</h3>
<p>REST, опять-таки, диктует, чтобы для представлений ресурсов использовались по возможности известные стадартные форматы данных. Если так получается — это сказка. Всего лишь по одному заголовку Content-type клиент полностью понимает, как работать с этим ресурсом.</p>
<p>Поэтому для работы с блог-контентом — лентами и постами — мы выбрали <a href="http://tools.ietf.org/html/rfc5023">AtomPub</a>, который специально для этого и создавался. Этот же формат используют, например, <a href="http://code.google.com/apis/blogger/">Blogger</a> и <a href="http://codex.wordpress.org/AtomPub">WordPress</a>. Теоретически это означает, что с Я.ру должны заработать клиенты вроде <a href="http://explore.live.com/windows-live-writer">Windows Live Writer</a> и <a href="http://www.codingrobots.com/blogjet/">BlogJet</a>. Хотя конечно надо будет поработать над интероперабельностью и подпилить что-то в паре мест. Если кому интересно поотлаживать это с клиентской стороны — опять таки пишите!</p>
<p>Что интересно, что изначально, <em>неблоговый</em> контент — профили людей — тоже были выражены через AtomPub. Штука в том, что он явно позволяет расширять набор атрибутов записей. В постах мы используем это, чтобы передавать чисто ярушные атрибуты: уровень доступа и запрещённость комментариев. Но принципиально это значит, что через Atom можно выразить что угодно. В итоге довольно долго профили у нас были такими вырожденными "постами", в которых большая часть контента была кастомной.</p>
<p>Под конец, буквально за неделю до запуска, мы всё же решили, что профиль — это не пост, и особенной пользы от Atom в этом месте нет. В итоге <a href="http://clubs.ya.ru/ya/replies.xml?item_no=92391">придумали свой кастомный формат</a>. Это не очень хорошо, и я с удовольствием послушаю идеи, какие известные форматы, выражающий понятия "профиль человека" и "клуб по интересам", можно поддержать. Пока я лениво думаю над тем, чтобы выдавать профиль в формате text/x-vcard, а списки друзей в text/directory или FOAF. </p>
<hr>
<p>Что нам даёт вся эта REST'овость? Я пока не знаю. Отчасти, это как раз эксперимент, чтобы понять, какие обещания REST реально воплощаются, а каким ещё надо помогать. Например, если блог-клиенты заработают с Я.ру, я буду считать это значительным достижением.</p>
<p>Если пофантазировать, то ещё может хорошо сработать наличие глобально универсального машиночитаемого идентификатора человека. Например, в <a href="http://api.yandex.ru/fotki/">API фоток</a>, который тоже REST'овый, можно будет реализовать функциональность "добавить в любимые авторы", основываясь как раз на ярушных URI...</p>
<h2>OAuth</h2>
<p>С авторизацией мы, по сути, обогнали всех :-). Мы реализовываем драфт <a href="http://oauth.net/2/">OAuth 2.0</a>, и я специально употребляю (кривой) глагол несовершенного вида, потому что мы апдейтим реализацию под новые ревизии драфта сразу, как только они выходят. Пока разрабатывались, их сменилось уже три :-). Теперь, после публикации, обновляться будет сложнее, потому что придётся думать про обратную совместимость.</p>
<p>С точки зрения реализации это тоже отдельный сервис. Тоже написан на Джанго, но по-своему интересен, например тем, что в качестве хранилища использует только Redis. Но это, наверное, отдельная история.