Как я начал делать модуль расширения для ADFS и попал в тупик

Должен сразу признаться, что программист я не настоящий. То есть, когда-то я был и настоящим — в смысле, мне деньги платили именно за написание программ. Однако это было более пятнадцати лет назад, программы я писал, в соответствии с тогдашней модой, в основном, на Delphi (ну или чуть раньше — ещё и на C/C++) — короче, на том, что сейчас, ну, совсем не модно и спросом не пользуется. А последние лет пятнадцать я зарабатывал почти исключительно системным администрированием, преимущественно, администрированием решений Microsoft, в особенности Active Directory и MS Exchange. И единственное, что в этой деятельности касалось программирования — это написание скриптов на, если так можно выразиться, языке программирования под названием Powershell.

Однако сияющие перспективы системного администрирования в плане добычи хлеба с маслом и с икрой как-то так за прошедшие полтора десятка лет потускнели, и решил я вспомнить старое ремесло. Но решил при этом постараться не сильно отдаляться от знакомых тем, чтобы все-таки хоть как-то использовать накопленные знания. В частности — знания о продуктах Microsoft, с которыми я эти пятнадцать лет имел дело. Ибо старый программистский багаж типа Delphi — это уже не модно совсем, икру с этим особо не добудешь,а идти в модные фронтендщики, меняя Powershell на JavaScript, чтобы потом конкурировать с новоиспеченными вайтишниками, крайне не хотелось: и возраст уже не тот, и «прелести» скриптового языка — вроде невозможности ловить ошибки на этапе компиляции — меня еще в Powershell достали.

Но тут я обнаружил трудность. Microsoft, как известно, некоторое время назад решила стать облачной компанией. А для этого — стала загонять своих пользователей в облака, для чего — явно решила уморить все свои замечательные локальные продукты для бизнеса, типа моего любимого Exchange. А это автоматически делает лишенным всякой перспективы разработку связанных с этими продуктами программ. Однако, подумав, я нашел, как мне показалось, приемлемый компромисс: написать расширение для службы Active Directory Federation Service (AD FS). Потому как эта служба, используемая в разнообразных сценариях аутентификации и авторизации в распределенных системах, имеет куда лучшие шансы выжить в современном мире, чем решения, заточенные на применение чисто «на земле»(on-premises). В частности, она может использоваться для авторизации доступа к приложениям в облаке Microsoft на основе аутентификации в Active Directory на земле. А потому использование знаний и опыта работы с этой службой (которыми я немного обладаю) может иметь хоть какую-то перспективу и на будущее.

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

Ещё в давние-давние времена, когда Microsoft только выпустила ныне уже старую систему Windows Server 2012 R2, в которой ADFS обрела нынешний вид, я, обучаясь по своей привычке очередным нововведениям по документации, обнаружил в этой документации подробное, почти пошаговое, руководство с примерами, как делать и подключать этот самый модуль расширения. Тогда мне это было не нужно, но факт я этот запомнил. И вот теперь, когда время настало, я это руководство отыскал. Не скажу, что это было просто — ибо Microsoft зачем-то пару раз за это время переделала свой сайт с документацией, отчего все ссылки потерялись. Но, в конце концов, цель моя была достигнута.

Итак, нужный документ был найден. И установил я среду разработки, и развернул я стенд для проверки работы модуля, и приступил. Выбор языка для реализации модуля был прост: поскольку сам модуль выполняется в среде .NET CLR и поскольку примеры кода были на C#, я не стал умничать, а выбрал именно этот язык. Благо я его и так немного знал, и с разными объектами .NET пришлось во время работы с Powershell немало познакомиться, да и сам язык напоминал многие мне ранее знакомые. Ну, а для того, чтобы совсем упростить себе жизнь, я не поленился и поискал (по именам используемых интерфейсов) пример реального модуля расширения. И такой пример нашелся — когда-то зачем-то кто-то из Microsoft выложил на GitHub модуль сопряжения с какой-то службой рассылки SMS — уже весьма не новый, довольно громоздкий, с какой-то малопонятной логикой, завязанной, видимо, на логику API этой самой службы, описания которого у меня не было — короче, в качестве примера малопригодный. Тем не менее, репозиторий с этим модулем я тоже стянул к себе, чисто на всякий случай.

Начальный план был прост и совершенно не амбициозен: написать и подключить минимальный модуль, который отдает ADFS нужные тому фрагменты HTML для страницы аутентификации, а потом, не мудрствуя лукаво, возвращает результат, что аутентификация успешно пройдена. То есть, если вкратце, в одной функции обратного вызова, которая для начала аутентификации, модуль должен отдать строку с элементом , содержащим текст, типа: «А вы точно не хакер?» и кнопку, типа, «Конечно, нет» (и плюс требуемые самим ADFS hidden input, в которых он контекст хранит), а в другой, которая для проверки — вернуть результат, что проверка пройдена. При этом, в силу особенностей ADFS, в качестве результата нужно возвращать не только признак прохождения аутентификации — значение null, но и добавить в передаваемую в качестве параметр коллекцию утверждений (claims) утверждение определенного типа (тип утверждения — это URI) со значением — типом метода аутентификации (это тоже URI, и он указывается при регистрации модуля). Нужно это все мне было больше для проверки правильности настройки стенда, подвохов я на этом этапе не ожидал. И как оказалось — зря.

Так вот. Собрал я с помощью копипасты из руководства и какой-то матери классы, реализующие нужные интерфейсы. Поменял из эстетических соображений значение для утверждения метода аутентификации: в примере использовался URI в виде типичного URL, а мне захотелось использовать схему (часть до двоеточия) urn. Написал скрипт по закидыванию в GAC сборки и регистрации в ADFS класса модуля. И приступил к тестированию. Первая пара итераций, убиравшая мелкие шероховатости из скрипта регистрации и фрагментов HTML, прошла нормально. Но вот потом процесс уперся в мертвую точку. В этот самый тупик

Несмотря ни накакие мои попытки, ADFS после возврата из функции обратного вызвова, которая должна была сообщить об успешной аутентификации, упорно ругался в своем журнале событий ошибкой ID 364 с сообщением «Microsoft.IdentityServer.RequestFailedException: No strong authentication method found for the request…», а в веб-интерфейсе аутентификации, соответственно сообщал о том, что произошла ошибка. То есть, ADFS ни в какую не хотел видеть утверждение с методом аутентификации, которое я ему возвращал. Я для начала вернул значение утверждения в то, которое было в руководстве (мало ли, вдруг ADFS там схему от URL хочет) — не помогло. Стало ясно, что дело серьезно.

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

В своей прошлой программистской жизни с затыком — своим или коллег — я сталкивался неоднократно. Но это было с программами, в которых можно было посмотреть под отладчиком, что происходит — вплоть до машинных команд и регистров процессора, да ещё и, при необходимости — в вызывающем модуле тоже: пусть исходников от него и не было, но и машинный код тоже мог дать намек, что же вызвающему модулю не так. А ещё это было в офисе. Где можно было подойти к коллеге (или — коллега мог подойти ко мне) и попросить помочь. Обычно это заканчивалось на стадии объяснения, как оно работает: в какой-то момент наступало озарение, и я (или коллега) со словами, переводимыми на русский литературный обычно словом «Эврика!», находил причину и приступал к ее устранению. Если же озарение не приходило, то часто помогал свежий не замыленный взгляд коллеги (или мой): ведь часто трудно не поверить себе, и понять причину, когда уже убедил себя, что оно должно работать и как именно должно, а вот свежий, не испорченный этим убеждением взгляд легче находит истину. Но здесь я был один, помощи ждать было неоткуда. Оставалось последнее средство.

Последнее средство выхода из затыка — делать хоть что-нибудь. Прежде всего — упрощать (хотя в данном случае упрощать, вроде как уже некуда). Потом — брать и вставлять куски, которые заведомо работают. И тут меня спас тот самый скачанный с GitHub модуль — с непонятной в целом логикой работы, который я к тому же не мог даже запустить у себя, ибо доступа к API внешней службы у меня со стенда не было. Но он где-то когда-то заведомо работал — а потому мог принести пользу и мне. Сначала я скопировал из него URI метода аутентификации — но это не помогло. Потом я взял код функции, производящей аутентификацию, вырезал из нее всё, кроме установки утверждения и возврата значения, и вставил этот код вместо своего на аналогичное место (мудро сохранив при этом отладочную печать в лог). И, о чудо — модуль заработал. Теперь осталось лишь понять, в чем же была ошибка, и устранить ее уже в своем коде

Понять это оказалось просто, сравнив строки отладочной выдачи в логе: вставленный код возвращал в качестве имени утверждения правильное (как я потом проверил по документации) значение «schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod», а мой (точнее, скопированный из руководства) код возвращал «несколько» другое, неправильное значение: «schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod». Просто так на глаз эту разницу увидеть трудно, но вот когда значения в логе оказались одно под другим, разница в длине из-за лишней буквы сразу вылезла. И, естественно, после отката всех панически внесенных изменений и исправления ошибки все заработало, как ожидалось.

В этом месте должна быть мораль. И если бы я был топ-менеджером из Microsoft, то мораль была бы проста: не надо переделывать сайт с документацией. Ошибка-то явно была внесена, когда чем-то автоматическим переделывались все ссылки с протокола http на протокол https: URI имени метода аутентификации, который выглядит как URL, и был обработан как URL прямо в примере кода. Если бы сайт не переделывали бы — ошибка бы не возникла. Но я не топ-менеджер Microsoft, а потому на ситуацию с ее документацией повлиять никак не могу. Поэтому для себя я сделал куда более скромный вывод: не доверять на 100% примерам на сайтах, даже если они — из официальной документации, и даже если ими кто-то некоторое время назад успешно воспользовался. Потому что в наше время документы пишутся и меняются не одним человеком (а иногда — даже вовсе ботом), и никто и ничто не гарантирует, что они не будут изменены внезапно.

PS Ссылки на использованные фрагменты документации и репозиторий я не привел, чтобы не нарушать ход повествования. Если они кому-нибудь вдруг окажутся нужны — пишите в комментарии, сообщу.

ссылка на оригинал статьи https://habr.com/ru/post/508804/

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *