Автоматически сгенерированные пароли в iOS 12

Если в вашем приложении есть функция регистрации, включающая в себя возможность или необходимость ввода новых имени пользователя и пароля, скорее всего, вас заинтересует нововведение в «iOS 12», которое я хотел бы обрисовать. Это сервис, который придумывает новые пароли для пользователя, автоматически подставляет их в нужные поля и безопасно хранит в «Keychain».
Автоматически-сгенерированные системой пароли являются наиболее стойкими к подбору (будучи случайно-сгенерированными последовательностями символов — с поправкой на настраиваемые ограничения, но об этом позже), избавляют пользователей приложения от необходимости придумывать последовательность самостоятельно и гибко настраиваются под нужды конкретного приложения. Поддержка новой функциональности довольно легко обеспечивается, тем не менее не без особенностей. Но обо всем по порядку.

Права и обязанности


В первую очередь, приложение должно заявить о своем желании пользоваться этим функционалом. В список «Capabilities» соответствующего «Target» необходимо иметь, во-первых, домен в списке «Associated Domains». Как ни странно, приложение должно иметь «Associated Domain», чтобы иметь возможность пользоваться сгенерированными паролями и хранить их в «Keychain» пользователя (две эти функции взаимосвязаны, и генерация не может быть использована отдельно от хранения).

Если приложение уже поддерживает использование общих учетных записей с вашим сайтом (т.н. «Shared Credentials»), то этот шаг уже позади. Также он может быть уже позади, и если приложение поддерживает «Universal Links» или другой механизм обработки внешних «URL»-ссылок.

Так или иначе, после добавления этой совместимости у приложения появится новое «Entitlement».

Помимо этой, более общей, совместимости приложение также должно иметь «Capatibility» «AutoFill Credential Provider» — это дает возможность приложению, при наличии разрешения от пользователя, пользоваться предложенными системой логинами и паролями. Добавление этой совместимости повлечет появление разрешения «AutoFill Credential Provider Entitlement».

К слову, добавление этой и других возможностей доступно только членам «Apple Developer Program».

В используемый приложением «Provisioning Profile» также должны быть включены перечисленные две функции.

Используемые зависимости


Добавление соответствующих совместимостей повлечет за собой появление фреймворка «AuthenticationServices» в списке «Linked Frameworks and Libraries» соответствующего «таргета». Этот момент обладает некоторыми особенностями, которые стоит упомянуть.

Во-первых, автоматическое добавление связанного фреймворка может не «сработать» с первого раза: при запуске приложения на реальном устройстве из моего экземпляра «Xcode» версии 10.1, приложение сразу «падало» ввиду отсутствия «AuthenticationServices». Ручное удаление фреймворка и добавление его обратно в список связанных компонентов решило проблему.

Во-вторых, автоматическое добавление фреймворка помечает его как «Required». Если ваше приложение подразумевает возможность работы «под» «iOS» версий ниже 12, это также вызовет его падение на этапе запуска «из-под» операционных систем более низких версий. «AuthenticationServices» доступны только для систем версий от 12-ой. Эта проблема решается отмечанием фреймворка как «Optional».

Поддержка в текстовых полях


Для поддержки функционала текстовыми полями используется переменная textContentType протокола UITextInputTraits. Класс UITextField, который, скорее всего, используется для ввода логина и пароля в приложении, уже реализует требования нужного нам протокола.

textContentType — это поле типа UITextContentType, содержащего лишь набор констант. Нужная нам в данный момент — newPassword, используемая для ввода нового, придумываемого в данный момент, пароля (не путать с просто password, используемой для ввода уже существующего пароля).

let passwordTextField = UITextField()

if #available(iOS 12, *) {
    passwordTextField.textContentType = .newPassword
}


Установка значения textContentType обернуто в проверку доступности «API», потому что, как и общий функционал, эта константа доступна только начиная с «iOS 12».

Помимо типа содержимого, текстовое поле обязательно должно обеспечивать безопасный ввод данных:

passwordTextField.isSecureTextEntry = true


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

С типом содержимого текстового поля связан интересный момент: если на экране не будет присутствовать другое поле, с типом содержимого username, автоматически-сгенерированный пароль предложен не будет. Это связано с тем, что функционал основан не просто на указанном типе контента текстоваого поля, а на эвристическом анализе содержимого экрана.

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

Стоит упомянуть, что если логин семантически является адресом электронной почты (и, следовательно, очень хочется иметь соответствующий вид клавиатуры), типы клавиатуры и содержимого допустимо сочетать:

let userNameTextField = UITextField()
userNameTextField.keyboardType = .emailAddress
userNameTextField.textContentType = .username


Требования к паролям


Часто пользовательские пароли должны соответствовать некоторым правилам (иметь определенную длину, включать в себя определенные символы и т.д.). Эти правила можно указать для того, чтобы система учитывала их при генерации паролей. Это делается через свойство passwordRules протокола UITextInputTraits. Например:

if #available(iOS 12, *) {
    passwordTextField.passwordRules = UITextInputPasswordRules(descriptor: "required: upper; required: lower; required: digit; minlength: 8;")
}


Свойство также доступно только начиная с «iOS 12».

Тип свойства — UITextInputPasswordRules. Инициализация — с помощью строки-дескриптора. Дескриптор имеет несложный синтаксис и состоит из простых требований к паролю, перечисленных через точку с запятой. Каждое требование — это пара «ключ-значение», разделенные двоеточием. Ключ представляет собой тип правила (например, «обязательно включает» — required), а значение — элемент, который должен следовать этому правилу (например, цифры — digit).

В примере выше дескриптор означает:

  • required: upper — необходимо присутствие хотя бы одной прописной буквы;
  • required: lower — то же для хотя бы одной строчной буквы;
  • required: digit — то же для хотя бы одной строчной цифры;
  • minlength: 8 — минимальная длина — восемь символов.


Подробное перечисление возможных ключей и значений можно найти в неплохой статье, опубликованной на сайте «NSHipster».

А «Apple» предлагает довольно удобного помощника по составлению дескрипторов, который предоставляет не только удобный способ их конструирования, но и проверку составленных дескрипторов в виде неограниченного количества сгенерированных примеров. Там же можно посмотреть, какие правила применяются по-умолчанию.

Валидация


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

Interface Builder


Что примечательно и в духе нашего времени, все перечисленные настройки текстовых полей можно выставить в «Interface Builder», вплоть до «Password Rule»:

image

Проверка функциональности


Функционал не сложный, но обладает рядом нюансов: при его настройке легко можно что-нибудь забыть. В этом случае в «debug»-сборках при активации соответствующего текстового поля в консоль будет выведена причина, по которой функциональность в данный момент не действует.

Например:

[AutoFill] Cannot show Automatic Strong Passwords for app bundleID: <...> due to error: Cannot save passwords for this app. Make sure you have set up Associated Domains for your app and AutoFill Passwords is enabled in Settings


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

1. iCloud Keychain;
2. AutoFill.

Заключение


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

Особенность довольно интересная и при правильном ее использовании вполне способна улучшить пользовательский опыт вашего приложения!

© Habrahabr.ru