Начну так. Соединение из OpenID и hCard — это дикий фонтанирующий рулез! Это чертово будущее онлайн-регистрации, которое все обязаны реализовать в своих продуктах прямо сейчас!!!

Вниманию непрограммирующей публики!

Итак, в Cicero появилась возможность логиниться с помощью OpenID. Что в первую очередь означает, что:

Теперь я приглашаю всех это попробовать, потестировать. Для чего вам понадобится ваш собственный OpenID. Это просто.

Во-первых, OpenID у вас может уже быть:

Однако не спешите с ним сразу на форум, чтобы основной кайф не пропустить — чуть ниже будет часть про hCard.

Если же у вас такого OpenID еще нет, то его можно завести. Даже нужно, потому что, поверьте мне наслово, через полгодика это будет распространенной и полезной вещью. Поскольку OpenID — это по сути некий ваш персональный URL, то неплохо бы выбрать его красиво. Здесь на выбор есть три варианта:

Второй вариант представляется мне наилучшим сочетанием простоты и получаемого эффекта, поэтому я его всем и рекомендую. Делается это так:

  1. Регистрируется OpenID, пусть это будет "http://username.videntity.org/"

  2. Идем на эту страницу, смотрим в исходник и находим там в head'е такую строчку:

    <link rel="openid.server" href="http://videntity.org/server">
    

    Это указатель на собственно механизм (скрипт), который проводит авторизацию.

  3. Дописываем в шаблон вашей страницы эту строчку, а также добавляем еще одну, которая скажет сервису, что ваша страница действует от имени зарегистрированного у них аккаунта:

    <link rel="openid.server" href="http://videntity.org/server">
    <link rel="openid.delegate" href="http://username.videntity.org/">
    

Это все. Теперь у вас есть страница с OpenID. Но перед тем, как его опробовать, давайте сделаем еще одну маленькую вещь (это по желанию).

hCard

OpenID — это классно и все такое, но это только часть фана. Было бы слишком скучно, если бы все люди именовались URL'ами, вас ведь не "http://.../" зовут, да? Однако этот URL — ваш персональный идентификатор — указывает на страницу. И предполагается, что на этой странице самое место, чтобы написать что-нибудь про себя. Например я в качестве своего OpenID использую вот эту страницу: http://softwaremaniacs.org/about/.

Я подумал, чтобы было бы замечательно, если бы форум мог с этой страницы сам прочитать, как вас зовут, запомнить это, и использовать в качестве вашего ника. Именно для таких вещей и служит микроформат hCard. Его используют на OpenID-страницах некоторые публичные провайдеры, например MoiKrug.ru и Videntity.org, поэтому если вы зарегистрируетесь у них, то ничего лишнего делать не нужно. Если же вы используете собственную страницу, как я описал выше, то в ней тоже нужно сделать hCard. Потому что это а) дико просто и б) не изменит внешний вид страницы.

Опуская подробности (которые можно найти в документации hCard), вот шаги по превращению вашей личной страницы в машиночитаемую карточку:

  1. Найти на странице блок, в котором лежат ваши персональные данные. Неважно, что это: какой-то <div>, <address>, <table> или вообще весь <body>, выбирайте по логике, как вам удобней.

  2. Добавить в этот тег class="vcard". Если там уже есть какой-то класс, допишите "vcard" через пробел class="something vcard".

  3. Найти, где написаны ваши имя с фамилией. Они могут уже лежать внутри какого-нибудь элемента — <h2>Иван Сагалаев</h2>. А могут быть просто написаны вольным текстом, тогда заверните это в span: <span>Вася Пупкин</span>.

  4. Добавить в этот тег class="fn".

Это все. Хотя, если на этой странице еще где-то присутствует ник, то его можно оформить тоже: <span class="nickname">...</span>.

Вот теперь можно идти в форум и логиниться, он должен автоматически подхватить, как вас зовут.

Остальная часть статьи — смачные подробности реализации.


Bzr-репозиторий http://softwaremaniacs.org/code/cicero/
Работающий форум http://softwaremaniacs.org/forum/

Библиотеки

Вся история с OpenID+hCard сделала Cicero сложнее в установке, потому что теперь форум использует кучу дополнительных библиотек:

Кроме того, нужно прописать две настройки, что подробно написано в файлике INSTALL в дистрибутиве. Но на самом деле, ничего особенно дикого там нет.

Авторизация (то есть аутентификация)

Признаюсь, я думал, что приблизительно зная принцип работы протокола, реализовать OpenID-клиента будет проще... Оказалось же, что это и в принципе не очень тривиально, да и с внятными описаниями и примерами проблема. Я нашел только страничку в Django-wiki да пример внутри самой py-openid (директория examples в исходном архиве). С первым проблема в том, что оно во-первых написано под старую версию библиотеки, а во-вторых там по ходу дела меняются названия и суть переменных в разных кусочках кода, что не способствует ясности :-). Официальный же пример очень трудно читать, потому что там помимо собственно авторизации еще в том же файле реализовано целиком веб-приложение вместе с WSGI-хэндлером, обработкой кучи ошибок и своей системой сессиий. Эдакий мини-фреймворк, который где-то там внутри заодно и OpenID занимается. Поэтому я постараюсь написать все подробно и пошагово.

Итак, суть OpenID-клиента в Django сводится к таким шагам:

  1. Показать формочку с полем ввода OpenID

  2. Принять POST из формы, отвалидировать его:

    • посмотреть, что введен URL (это делается средствами Django)
    • вытянуть с этого URL данные об OpenID-провайдере (это делается одним вызовом в библиотеку)
  3. Выдать пользователю в браузер редирект на его OpenID-провайдера, напихав в этот URL всякую временную информацию для авторизации и того, что делать после авторизации (URL создается функцией из библиотеки).

  4. Дальше пользователь сам разбирается со своим провайдером, что в лучшем случае происходит для него незаметно, если он туда уже залогинен.

  5. Сервер провайдера присылает обратно в Django-приложение подтверждение авторизации вместе с той кучей данных, которые мы присылали туда в шаге 3.

  6. Проверить присланное на соответствие посланному (еще один вызов в библиотеку).

  7. Если все хорошо, достать или создать джанговского пользователя, соответствующего OpenID URL'у и зафиксировать его в кукесах стандартными средствами Django.

Сложная часть в том, где это все в коде расположить. Я видел несколько примеров джанговского OpenID'шного кода, где все это делалось внутри двух (а то и одной) view-функций, совершенно игнорируя хорошие джанговские механизмы. Поэтому я решил все сделать правильно (то есть, как мне кажется правильным :-) ).

Начало

Первые 2 шага — это практическии стандартная view, обрабатывающая форму: на GET форма показывается, на POST — валидируется и сохраняется. Разница только в том, что вместо сохранения объекта успешным выходом из формы будет URL редиректа пользователя к своему провайдеру. Из-за этой особенности view придется написать вручную, а не пользоваться generic'ами:

def login(request):
  from cicero.forms import AuthForm
  if request.method == 'POST':
    form = AuthForm(request.session, request.POST)
    if form.is_valid():
      after_auth_redirect = form.auth_redirect(post_redirect(request))
      return HttpResponseRedirect(after_auth_redirect)
  else:
    form = AuthForm(request.session)
  return render_to_response(request, 'cicero/login.html', {'form': form, 'redirect': post_redirect(request)})

Я считаю очень важным, чтобы view-функции обработки форм строились по одному паттерну: сама view занимается фактически только HTTP-диспетчеризацией, а основная работа — будь это сохранение объекта, отсылка email'а или проверка OpenID URL'а — должна делаться внутри объекта формы. Хотя в примерах я встречал, например, такую вещь:

def login(request):
  if request.method == 'POST':
    form = AuthForm(request.POST)

    if form.is_valid():
      try:
        # проверка OpenID URL'а
      except Ошибка1: 
        form.errors[...].append('...')
      except Ошибка2:
        form.errors[...].append('...')

То есть по сути единый процесс проверки ввода — валидация — разделяется зачем-то на два и лежит в двух местах: стандартная валидация, а затем код, который пытается ее извне дополнять. Выглядит криво. Поэтому я запихнул это все в форму в стандартное место валидации поля с URL'ом:

class AuthForm(Form):
  openid = URLField(label='OpenID', required=True)

  # ...  

  def clean_openid(self):
    from cicero.auth import get_consumer
    from yadis.discover import DiscoveryFailure
    from urljr.fetchers import HTTPFetchingError
    consumer = get_consumer(self.session)
    errors = []
    try:
      self.request = consumer.begin(self.clean_data['openid'])
    except HTTPFetchingError, e:
      errors.append(str(e.why))
    except DiscoveryFailure, e:
      errors.append(str(e[0]))
    if hasattr(self, 'request') and self.request is None:
      errors.append('OpenID сервис не найден')
    if errors:
      raise ValidationError(errors)

Как видно, форма состоит из одного поля. Его тип (URLField) обеспечивает самую базовую валидацию — похоже ли оно на URL вообще. Чтобы дополнить ее своей, надо в форме определить метод вида "clean_названиеполя", который Django и вызовет. Весь метод строится вокруг одного вызова клиентского объекта OpenID-библиотеки — "consumer.begin", который обращается по переданному URL'у и проводит всю черновую работу: запрашивает дремучие XML-описания, сверяет версии, парсит HTML на предмет <link rel="openid.server" ...> и все такое. Если что-то идет не так, выкидывает исключения, которые тут же сваливаются в список найденных ошибок. Если ошибок нет, то в форме создается поле request, которое будет использоваться дальше для построения редиректа.

Кстати о "клиентском объекте"... У библиотеки есть две части — server и consumer (зачем было client'а называть consumer'ом?). Клиент создается не очень тривиально, почему и вынесен в отдельную функцию get_consumer:

from openid.consumer.consumer import Consumer
from openid.store.filestore import FileOpenIDStore
from django.conf import settings

def get_consumer(session):
  if not settings.OPENID_STORE_ROOT:
    raise Exception('OPENID_STORE_ROOT is not set')
  return Consumer(session, FileOpenIDStore(settings.OPENID_STORE_ROOT))

Ему для существования нужны две вещи. Во-первых, некий объект сессий, на роль которого удачно подходят джанговские сессии, и которые и передаются туда (из вьюхи login в AuthForm, а из нее — в get_consumer). А во-вторых, специальный объект-склад, где библиотека будет хранить временные данные авторизации, и который вынуждает таки требовать отдельной настройки при установке Cicero, потому что директорию на локальном диске придумать "по умолчанию" не получится...

Есть там, правда, еще два способа организации склада. Один — хранение в БД — мне не понравился, что ли, эстетически: лишние таблицы выглядят большим злом, чем директория. А второй — некий "тупой" режим, для которого собственно хранения не требуется, но он считается несекьюрным, и что главное, я не смог заставить его работать :-). Если у кого получится, напишите!

Итак, если форма выдает ошибки, они показываются — тут все стандартно. Если ошибок нет, то из формы надо получить URL провайдера, на который отправлять редирект браузеру, для чего в форме вместо метода "save()" есть метод "auth_redirect()", который его "конструирует". Другого слова и не подобрать, потому что процесс сложнее, чем кажется:

class AuthForm(Form):
  # ...

  def _site_url(self):
    from django.contrib.sites.models import Site
    site = Site.objects.get_current()
    return 'http://' + site.domain

  def auth_redirect(self, target):
    from django.core.urlresolvers import reverse
    site_url = self._site_url()
    trust_url = settings.OPENID_TRUST_URL or (site_url + '/')
    return_to = site_url + reverse('cicero.views.auth')
    self.request.return_to_args['redirect'] = target
    return self.request.redirectURL(trust_url, return_to)

URL конструирует объект request, который получается из библиотеки во время опроса провайдера. В него заворачиваются два параметра, которые провайдеру нужны:

В обоих параметрах используется site_url — корень сайта. В принципе, его можно достать из переменной запроса "HTTP_HOST", но в Django так не принято. Для этого существует встроенное (и, кажется, ставящееся автоматически) приложение Sites, которое создает в базе табличку с двумя полями: красивым названием текущего сайта и его доменом. Хорошо это например тем, что приучает пользователей использовать один канонический URL сайта, а не разные варианты (с "www.", без "www." и т.д.).

И последний важный параметр, который передается в URL — target. Это то, куда надо будет отправлять пользователя уже после авторизации. Что странно, этот "маловажный" момент пропускают все как один примеры авторизации по OpenID, говоря, что "теперь пользователь авторизован, делайте с ним все, что вам нравится". В традиционных схемах это не проблема, потому что авторизация происходит сразу в том месте, где обрабатывается первая форма, и туда можно передать какой-нибудь параметр "redirect" или использовать реферер. В OpenID этот параметр приходится гонять по всему пути через провайдера.

Завершение

Сама view, которая принимает редирект от провайдера, очень простая, практически из документации:

def auth(request):
  from django.contrib.auth import authenticate, login
  user = authenticate(session=request.session, query=request.GET)
  if not user:
    return HttpResponseForbidden('Ошибка авторизации')
  login(request, user)
  return HttpResponseRedirect(request.GET.get('redirect', '/'))

А всю работу делают функции authenticate и login из джанговской системы аутентификации. Они занимаются непосредственно опознанием и запоминанием пользователя. Причем опознание делается через подключаемые в настройках бэкенды — классы, которые реализуют разные виды аутентификации. Соответственно для OpenID тоже нужен свой бэкенд. Лежит он в отдельном файле auth.py и выглядит примерно так:

class OpenIdBackend(object):

  def authenticate(self, session=None, query=None):

    # проверить данные, присланные удаленным сервером,
    # соответствуют ли они посланным в начале процесса
    # в query передаются параметры GET из запроса
    consumer = get_consumer(session)
    info = consumer.complete(query)

    # если нет, вернуть None -- стандартное поведение для backend'а
    if info.status != SUCCESS:
      return None

    from cicero.models import Profile
    try:

      # найти пользователя, если такой OpenID уже есть в его профиле
      profile = Profile.objects.get(openid=info.identity_url)
      user = profile.user

    except Profile.DoesNotExist:

      # если нет, создать пользователя и профиль
      import md5
      unique = md5.new(info.identity_url).hexdigest()[:23] # 30 - len('cicero_')
      user = User.objects.create_user('cicero_%s' % unique, 'user@cicero', User.objects.make_random_password())
      profile = user.cicero_profile
      profile.openid = info.identity_url
      profile.save()
    return user

Надо заметить, что несмотря на то, что решение об успешной аутентификации принимается OpenID-библиотекой, и джанговская таблица пользователей тут не участвует, записи в ней все равно создаются. Так рекомендуется делать, потому что в Django есть много полезных сервисов, завязаных именно на эту таблицу пользователей: права, сообщения на сайте, админка.

Для OpenID пришлось также расширить профиль на два поля, потому что встроенной таблицы User не хватает:

Из-за появления отдельного понятия имени пользователя пришлось переписать его отображение (раньше для него использовался User.username). Теперь оно возвращает Profile.name, если его нет — Profile.openid, если его нет — User.username, который есть всегда. Выглядит красиво, но к сожалению добавляет один или даже два отдельных SQL-запроса на каждую статью при выводе топика! Пока я оставил это так, но надо будет исправить.

Выемка Profile.name

На закуску осталась автоматическая выемка имени пользователя с OpenID-страницы. Делается это в момент сохранения профиля при условии, что OpenID в нем есть, а name — нет. Принцип работы прост: вытянуть файл по URL'у, распарсить HTML в поисках элемента с классом "vcard", внутри него найти элементы с классами "nickname" или "fn" ("ник" и "имя", соответственно), записать их содержимое в name.

Для парсинга HTML используется библиотечка BeautifulSoup, и выглядит он действительно просто и красиво:

import re
from BeautifulSoup import BeautifulSoup
soup = BeautifulSoup(content)
vcard = soup.find(None, {'class': re.compile(r'\bvcard\b')})

В следующий раз, когда вам скажут, что HTML парсить дико трудно, а XML дико просто, вспоминайте о том, что этот вопрос надо решать не вам лично, а библиотекам. Скорость же этого парсинга в данном случае не играет почти никакой роли (этот парсинг все равно сильно быстрее собственно загрузки страницы), как и во многих других случаях.

Дальше аналогичным образом внутри vcard ищутся элементы ника или имени. Здесь надо учитывать интересный паттерн, сложившийся в микроформатах. Обычно элемент с нужным классом содержит данные внутри себя:

<span class="nickname">Maniac</span>

Но если этот элемент — <abbr>, то внутри него написано некое "сокращенное" представление данных, а развернутое переносится в атрибут "title":

<abbr class="fn" title="Иван Сагалаев">Иван С.</abbr>

Для обработки таких случаев пришлось написать маленькую подфункцию:

def _parse_property(class_name):
  el = vcard.find(None, {'class': re.compile(r'\b%s\b' % class_name)})
  if el is None:
    return
  if el.name == u'abbr' and el['title']:
    return el['title'].strip().encode(settings.DEFAULT_CHARSET)
  else:
    result = ''.join([s for s in el.recursiveChildGenerator() if isinstance(s, unicode)])
    return result.strip().encode(settings.DEFAULT_CHARSET)

Пока на этом я остановился. Следующее, что предстоит сделать — это визуально разделить гостей и пользователей, имеющих OpenID. И там я обещаю кое-что интересное :-).

Комментарии: 39

  1. ilya

    Иван, это очень жирный пост!
    Большое спасибо что не ленишься писать так все подробно и ясно.

  2. DM

    Спасибо огромное, просто и понятно о OpenID. И сам буду теперь его юзать ;)
    Понравилось =)))

  3. FX Poster

    Пост еще не дочитал, времени нет. Но вот вопрос есть, чисто технический, - ты говоришь, что OpenID сервер модет быть установлен на любом серваке. Так вот - а как все сервера взаимодействуют между собой?

    Приведу пример, если не сильно понятно: ты используешь в качестве сервера videntity.org, у меня есть свой OpenID сервер, но он установлен на моем сервере и к videntity.org я никакого отношения не имею. На каких правах я смогу по своему url'у зайти на твой форум, если никто, кроме моего OpenID-сервера об этом url'е не знает.

    PS. А почему у тебя в форума никаких rel="openid.*" нет?

  4. FX Poster

    Вопросы сняты :)

  5. WiRED

    Огромное спасибо за обстоятельный и полезный рассказ о технологии!

    А теперь - ложка дегтя :)

    Все это очевидный пример, когда удобное для реализации вступает в противоречие с потребностями пользователя.

    Достаточно большому количеству людей нужны МНОГО (больше одного то есть) аккаунтов, более того - никак не связанных друг с другом. Это ведь еще и прайваси, в конце концов.

  6. ph

    Действительно, спасибо, информация очень полезная. я вот, вообще, как то краем уха про это слышал, после прочтения решил прикрутить везде у себя :)

  7. Иван Сагалаев

    WiRED, помимо того, что в Cicero остается возможность гостевого постинга, еще и в принципе никто не мешает человеку иметь несколько OpenID, почему нет? Смысл в том, что тут решение о том, сколько плодить логинов, человек принимает сам.

    В смысле спамерства это не особенно страшно, потому что потребителей рекомендуется трактовать любой новый логин как "недоверенный", и только если от него идет нормальный контент, включать его в "белый список".

  8. Вася Триллер

    Класс! Нужно будет непременно заюзать!

  9. Тимур Вафин

    Вот я одного не понимаю в авторизации по OpenId: что мешает злоумышленнику создать сервер, который быдет идентифицировать всех подряд, т.е. всегда говорить "да, этот пользователь тут есть!"

  10. Константин

    Отлично! Теперь осталось сделать openid-аутентификатор для самой джанги.

  11. Иван Сагалаев

    Тимур, я как раз коснулся этого чуть выше в комментарии про белый список. Действительно, проверка OpenID не означает для сервиса, что пользователю можно доверять. Доверие строится потом, на том основании, что владелец OpenID пишет нормальный контент и не дает пользоваться своим OpenID спамерам.

    То есть эта регистрация не дает никакой дополнительной защиты сервисам относительно того, что есть сейчас. Она в этом смысле не отличается от посылки подтверждения на email: ведь email'ов тоже можно регистрировать сколько угодно.

    Смысл OpenID в основном в удобстве для пользователей.

  12. Максим Деркачев

    Одна небольшая проблема.

    openid = URLField(label='OpenID', required=True)
    
    1. Поле по стандарту называется openid_identifier (чтобы автодополнялка включалась)
    2. Поле для OpenID-идентификатора не должно требовать ввода спецификатора протокола (http://)). Пользователь должен иметь возможность ввести username.livejournal.com, а не http://username.livejournal.com/.
      К тому же, валидным OpenId-идентификатором может быть не только URL, а также XRI, например, iname (выглядит как =myiname). Причем в последнем случае меняется механизм аутентификации, который ресолвит i-name в URL проверки юзера у его регистратора, и отправляет клиента туда.
      Библиотека сама проверяет валидность и тип идентификатора, сама добавляет все что нужно.
      UrlField же требует, чтобы приехал URL по полной схеме. Так что тут лучше обойтись простым CharField, и всю валидацию делегировать библиотеке, она в данном случае лучше знает.
  13. Максим Деркачев

    Анонимных провайдеров можно просто вычислить - при вводе нового идентификатора ("регистрации") попробовать сначала авторизоваться у провайдера с каким-нибудь левым именем, которого быть уж точто не должно. Например, md5-хешем от случайного числа :)

  14. Иван Сагалаев

    Максим, оба исправления сделал, спасибо!

    Правда только:

    Поле по стандарту называется openid_identifier (чтобы автодополнялка включалась)

    Доверяй, но проверяй :-). На самом деле "openid_url": http://openid.net/specs/openid-authentication-1_1.html#anchor6 (там в пункте 3.2.1 последний буллит).

  15. Максим Деркачев

    В спецификации 2.0 - openid_identifier
    Видимо, чтобы поставить ударение на присутствие не-URL-идентификаторов :)
    http://openid.net/specs/openid-authentication-2_0-11.html#initiation

  16. Иван Сагалаев

    Ах, 2.0... Но она пока еще proposed draft :-). Причем я думаю, что это как раз та вещь, которая действительно может поменяться, потому что обратная совместимость здесь реально полезней теоретической чистоты. Кстати, на http://www.livejournal.com/openid/ — тоже openid_url.

  17. Сергеев Сергей

    отлично, работает и читает hCard
    вот только с визуальной точки зрения пользователи не выделяются :
    т.е. нельзя различить, например, полных тёзок :)

  18. hidded

    Хм. Мне, по какой-то неизвестной причине, аутентифицироваться не удалось.

  19. Антон Морозов

    Иван, а можно вас попросить сделать более подробный вывод ошибок авторизации? Если у вас будет время, конечно.

    Дело в том, что у меня при попытке входа Cicero ругается на "Ошибку авторизации". При этом авторизация на моем OpenID-сервере проходит удачно. На http://livejournal.com/openid/ логинюсь без проблем.

    При авторизации указываю URL http://antonmorozov.ru
    Скрипт сервера брал отсюда: http://siege.org/projects/phpMyID/

    Большое спасибо!

  20. Максим Россомахин

    Иван, крайне заинтересовался — наконец-то можно показать на практике, зачем может пригодиться hCard! Ну т.е. я это понимаю, но вот разработчиков, которых мне доводилось агитировать в пользу микроформатов, как-то не вставляла абстракция (open data, децентрализация и т.п.).

    Могу ли я воспользоваться этой идеей в своём докладе о микроформатах для РИТ 2007?

  21. Иван Сагалаев

    Максим, я вот теперь сижу и думаю, как я сам-то не додумался это предложить :-). Конечно можно и нужно. Мне кажется, что микроформатам сейчас именно этого и не хватает — полезных реализаций.

  22. Денис Радченко

    Спасибо за описание, хотел реализовать OpenID авторизацию, но было очень мало информации.

  23. dottedmag

    Неплохо было бы сначала почитать переписку в почтовом листе разработчиков OpenID, чтобы понять, почему идентичное решение уже было один раз отвергнуто ;)

  24. Николай

    Суть в том, что Yadis (если не ошибся) сам может переносить данные о пользователе, так что hCard это просто дубляж возможностей OpenID

  25. Antono Vasiljev

    Да... Есть там такой механизм sreg. А дублировать - зачем это?

  26. Иван Сагалаев

    Нет, ну что значит "дублировать"? hCard появился и раньше OpenID, и раньше SRE. Так что это SRE — альтернативная технология, а не наоборот. Другое дело, что это не конкуренты. SRE решает более узкую задачу с меньшим количеством эвристики. hCard (и остальные микроформаты) — более богатая инфраструктурая, из которой можно вытянуть больше информации.

    Основной смысл для OpenID-клиента читать hCard в том, что эта hCard там может быть, и юзера можно магическим образом избавить от заполнения профиля.

  27. Виктор Гришко

    Очень интересная фишка.

    Однако, вопрос: я попробовал запостить пост на тестовом форуме. Аккаунт использовал на moikrug.ru. Все отлично идентифицировалось. Однако, в форуме я фигурировал под своим именем и фамилией. Если же у меня есть полный тезка, то он на этом же форуме будет с такой же фамилией и таким же именем. И в случае, например, спамерства, шишки от недовольных пользователей могут посыпаться и на меня. Согласитесь, не каждый начнет разбираться с тем, с какого домена пришел данный пользователь... Мало того, если бы я не прочитал сейчас про openId, я бы подумал, что мой аккаунт взломан и кто-то использует мои данные (это, конечно, в случае, сли я неопытный пользователь). Так что вещь, конечно, замечательная, но без широкой информированности масс может вызвать различные казусы. А жаль....

  28. Максим Россомахин
  29. nobody

    хотел спросить... а не будет ли такая ситуация что кто-то может воспользоваться моим openid чтобы писать от моего имени? Это ведь не секурно... или я не прав?)

  30. Иван Сагалаев

    Нет, для этого человеку надо будет уметь логиниться от вашего имени в тот сайт, на котором OpenID. То есть в ваш блог, ваш ЖЖ и т.д. Если он умеет это делать, то он и так уже может от вашего имени что-то публиковать.

  31. [...] шаг где-то рядом с OpenSocial. Это способ создать свой “профиль” вне социальных сетей, привязанный к OpenID. Кажется, немного - но будет только [...]

  32. [...] Маниакальный Веблог » OpenID и hCard в Cicero [...]

  33. opium

    Целых 2 неточности в классе AuthForm: тут не описан конструктор формы (ага, конструктор получает только request.POST, а request.session нужно запихивать туда отдельно) и опечатка. Вместо

    try:
      self.request = consumer.begin(self.clean_data['openid'])
    

    должно быть

    try:
      self.request = consumer.begin(self.cleaned_data['openid'])
    

    я когда делал на обоих граблях застрявал. исправьте для будущих поколений (%

  34. n0uk

    Спасибо за статью, благодаря ей только что прикрутил openid к своему блогу на django.
    На данный момент (Django 1.0, python-openid 2.0.2-1, python-2.5) пришлось исправить:

    1) В класс AuthForm добавить конструктор и изменить в функции clean_openid параметр self.clean_openid['openid'] на self.cleaned_openid['openid'] (как писали выше):

    class AuthForm(Form):
        ...
    
        def __init__(self, session=None, *args, **kwargs):
            self.session = session
            super(AuthForm, self).__init__(*args, **kwargs)
    
        ...
    
        def clean_openid(self):
            try:
                self.request = consumer.begin(self.cleaned_data['openid'])
                ...
    

    2) Функция complete класса openid.consumer.consumer.Consumer на данный момент (версия python-openid - 2.0.2-1) принимает два параметра, помимо query, еще и return_to - тот самый который был передан провайдеру:

    return_to = site_url + reverse('cicero.views.auth')
    

    3) Проверка Response от провайдера (в бэкенде для аутентификации) было:

    # если нет, вернуть None -- стандартное поведение для backend'а
    if info.status != SUCCESS:
        return None
    

    а надо:

    # если нет, вернуть None -- стандартное поведение для backend'а
    if info.status != "success":
        return None
    

    4) В класс OpenIdBackend необходимо добавить функцию get_user

    class OpenIdBackend(object):
    
        ...
    
        def get_user(self, user_id):
                try:
                        return User.objects.get(pk=user_id)
                except User.DoesNotExist:
                        return None
    

    Остальное по мелочи.

  35. Иван Сагалаев

    Код в посте блога в любом случае не стоит воспринимать, как полностью работающий. Это примеры для иллюстрации. Работающий же код лежит в svn://softwaremaniacs.org/cicero, и там вообще довольно много поменялось за полтора года.

  36. Song

    Иван, скажите а для чего Вы на своей страничке http://softwaremaniacs.org/about (которая является Вашим OpenID идентфикатором) вписываете openid.delegate на тот же идентификатор?

    Делегат нужен, если бы Вы хотели авторизоваться куда-либо под другим идентификатором, как я понимаю.

  37. Иван Сагалаев

    Это артефакт того, что ссылки на сервер и делегат прописаны в одном месте в коде, и проверять там "а не уже ли это та самая страница" было неудобно. Поэтому оно осталось так, благо не мешает.

  38. stas.er.it@gmail.com

    Скажите, а что бы "запустить" использование микроформатов на странице, обязательно вставлять отдельный внешний тег с class='vcard' или можно просто добавить через пробел class='megabox vcard' к существующему тегу?

  39. Ivan Sagalaev

    Да, достаточно добавить. Класс должен быть, но не обязан быть единственным.

Добавить комментарий