Показаны сообщения с ярлыком grails. Показать все сообщения
Показаны сообщения с ярлыком grails. Показать все сообщения

воскресенье, 13 января 2013 г.

Grails: Хитрые грабли на примере сохранения и валидации пароля

Часто натыкаюсь на такую запись:
class User {
  String email
  ...
  String password

  static constraints {
        password(nullable: false, blank: false, minSize: 6)
  }

}

И потом делают валидацию по этому полю а перед сохранением перезаписывают его хешом, например:
user.password = params.password.encodeAsSHA256()

Это некрасиво потому что в этом случае описание поля идёт в разрез с его презентацией на уровне БД. Описание модели должно описывать доменный объект с аспектом как его сохранять в БД, и валидациия указывается уровня БД а не биндинга в контроллере.
Об этом нужно помнить. Например при сохранении не уникального значение в unique поле вы получите исключении на уровне БД а не ошибку на уровне валидатора формы.
Кстати этот пример плох ещё тем что пароль не посолили.

Хеш будет иметь всегда фиксированный размер (в 64 символа в случае с SHA256). Даже у пустой строки, ага.
Если не указан максимальный размер текстового поля то его размер на уровне БД будет 255 символов. А использовать будем всегда только 64, т.е. имеем оверхед.
Или например если вы не указали максимальный размер поля, то он не будет валидироватся на уровне формы и вы получите исключение при сохранении в БД - ещё один наглядный пример что валидация форм не всегда коррелирует с презентацией на уровне БД.

Значит нам нужно указать размер поля, например так:
password(nullable: false, blank: false, maxSize: 64)
Хочу обратить внимание что ещё один аргумент против указания характеристик пароля в описании поля в которое будет сохранён его хеш, потому что в таком случае пароль будет ограничен в 64 символа.
Ограничивать размер пароля совершенно глупо, чем длиннее пароль тем выше его надёжность. Я например могу использовать целую фразу и вставлять её автоматически например из брелока.
Какая вам разница какой длины мой пароль? Хоть весь текст Войны и Мира, всё равно ведь в вашей БД он займёт 64 символа. Главное чтобы уложилось в разумное ограничение на длину POST запроса.
Кстати также глупо ограничивать или навязывать мне набор символов, например чтобы обязательно были в нём цифры. Пароль из шестнадцати обычных латинских букв надёжней пароля из шести крякозяблов "qwert$123".

Заключение

Я бы вам посоветовал вот такое описание поля пароля
password(nullable: false, size: 64..64, bindable: false)
Где размер строго 64 символа на хеш (зависит от алгоритма хеширования), и ещё отключен биндинг на всякий пожарный.

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

Надеюсь эта маленькая заметочка наглядно показала вам сразу несколько ловушек в Grails.
По хорошему для пароля нужно создать отдельный пользовательский тип или constraint.
У кого есть свободное время, сделайте пожалуйста плагин, я был бы очень благодарен.







среда, 15 августа 2012 г.

Grails GORM: Избегайте HQL и уже тем более SQL запросов

GORM построен поверх Hibernate а в нём есть два API для работы с БД: Hibernate Query Langauge (HQL) и Criteria API.
HQL по сути тот же SQL, только не специфичный для конкретной БД:
def results = Book.findAll("from Book as b where b.title like 'Lord of the%'")

Criteria API это объектно ориентированный подход к построению запросов. От этого он может получатся менее выразительным и громоздким. Но в замен вы получаете более удобный механизм генерации сложных запросов в ООП стиле, вместо генерирования HQL как текста (а значит и меньше ошибок связанных с некорректным HQL):
def c = Account.createCriteria()
def results = c {
    between("balance", 500, 1000)
    eq("branch", "London")
    or {
        like("holderFirstName", "Fred%")
        like("holderFirstName", "Barney%")
    }
    maxResults(10)
    order("holderLastName", "desc"

Кроме того, вам не нужно отдельно учить синтаксис SQL и HQL, что позволит новичкам быстрей разобраться с этим кодом. И уж тем более новичкам будет сложней сделать ошибку, поскольку запрос становится статически типизированным и ваша IDE может подсказать автодополнение а компилятор ругнутся на ошибку.
Если в Java иногда предпочитают HQL за его краткость, то Grails предоставляет удобный DSL для Criteri API, так что даже этот аргумент теряет свою важность.

И самое главное, запросы в Criteria API стиле могут быть программно проанализированы и это позволяет покрыть интеграционными тестами код который работают БД. Grails предоставляет просто шикарный тестовый фреймворк который позволяет тестировать работу с доменными объектами имитируя работу с GORM через лёгое in-memory хранилище.

Самое вкусное что большую часть простых запросов вы можете выполнить через механизм динамических методов с префиксом findBy (finders, файндеры):
def book = Book.findByTitle("The Stand")
book = Book.findByTitleLike("Harry Pot%")
book = Book.findByReleaseDateBetween(firstDate, secondDate)
book = Book.findByReleaseDateGreaterThan(someDate)
book = Book.findByTitleLikeOrReleaseDateLessThan("%Something%", someDate)
Внутри себя Grails конвертирует их в Criteria API запросы.
По возможности старайтесь использовать такие динамические файндеры они более краткие и очень выразительные.

И напоследок, обязательно изучите руководство по GORM'у чтобы не плодить проблем. Именно запросы в БД чаще всего являются узким местом в вашем приложении из-за которого проседает производтельность.
К сожалению в GORM очень много ловушек многих из которых избежать вам помогут три статьи евангелиста Grails Питера Ледбрука (Peter Ledbrook):
  1. GORM GOTCHAS (PART 1) Перевод на русский
  2. GORM GOTCHAS (PART 2) Перевод на русский
  3. GORM GOTCHAS (PART 3) (перевода на русский нет)