Нам и тут хорошо. Как мы наносим Йошкар-Олу на IT-карту России

Всем привет! В этой статье хочу поделиться своим опытом развития IT-движухи в Йошкар-Оле и дать советы, которые помогут вам начать развивать сообщество разработчиков в вашем городе.

Полигональный гофер на нашем первом Go-митапе.
Полигональный гофер на нашем первом Go-митапе.

Что вы знаете про Йошкар-Олу? Если вы сразу не спросите: «А это вообще где?», то, наверное, вспомните, что у нас есть «Йошкин кот». Или про не самую обычную набережную, которая нам досталась в наследство от бывшего главы республики Марий Эл. Но у нас много интересного. Например, несколько десятков IT-компаний и несколько сотен разработчиков, которые работают в этих компаниях или на фрилансе. В городе есть сильный технический вуз, что способствовало появлению IT-сообщества и довольно большого числа компаний для города с населением 274 000 человек.

Но не всё так прекрасно. Многие разработчики уезжают в Москву, Санкт-Петербург и другие крупные города. Например, из 30 человек, с кем я учился в одном физмат-классе в школе, в Йошкар-Оле осталось около десяти. У многих встречается аргумент: «Там больше возможностей, движухи, разных тусовок разработчиков». Но почему бы не сделать сообщество разработчиков в Йошкар-Оле? Мне нравится город, и я не хочу отсюда уезжать. Я решил сделать первый шаг и начал проводить митапы. И неожиданно это запустило целую волну интереса к этой теме у других разработчиков.

Go делать митап

Началось все с того, что я увлекся языком программирования Go. Мне очень хотелось, чтобы этот язык начали использовать и в компании iSpring, где я работаю. Я проводил семинары, обучающие курсы, и пару лет назад в iSpring тоже начали писать на Go. Одна из крутых особенностей Go — сильные и очень открытые сообщества, они есть в разных городах и странах. Например, у гоферов принято отмечать новые релизы, которые случаются каждые полгода. Такое же сообщество захотелось сделать и в Йошкар-Оле.

Первый Go-митап в 2018 году. На него пришло около 20 человек. 
Первый Go-митап в 2018 году. На него пришло около 20 человек. 

Первый Go-митап я провел в 2018 году. Специально для него был сделан полуметровый полигональный гофер. На митап пришло не больше 20 человек: около десяти человек из моей компании, еще человек пять из Института Программных Систем, который готовит программистов, и 3-4 человека со стороны. Было очень приятно видеть незнакомых людей — это означало, что в Йошкар-Оле есть разработчики, которым это интересно. С другой стороны, хотелось, конечно, больше участников.

Примерно тогда же я случайно познакомился с гофером Искандером Шариповым. Он организует Go-митапы в соседней Казани. Он начал делать их еще в Нижнем Новгороде, когда работал в Intel. А потом перебрался в Казань и начал здесь развивать сообщество. Я начал ему помогать, вместе с коллегами ездили выступать на его митапы. Брали с собой оператора, записывали выступления и выкладывали их на канал iSpring Tech. Такие каналы для разработчиков есть у многих крупных компаний. И мы решили сделать также.

В Казани уже было довольно уютное Go-сообщество и интересные митапы. Конечно, ехать туда не так далеко, но хотелось свое. И в декабре 2019-го я предпринял вторую попытку. Правда, решил начать с митапа по PHP. Этот язык популярнее Go, и больше шансов собрать людей. На Habr вышла статья Алексея Корнеева из Skyeng. В ней он писал, что собирает PHP-сообщества из разных городов и предлагал делать митапы. Я, недолго думая, добавил туда себя, оставил контакты. И занялся организацией своего мероприятия. Ребята из Skyeng связались и предложили помочь.

Второй заход

На том PHP-митапе я не знал половину людей, хотя город у нас маленький. Оказалось, что разработчиков в Йошкар-Оле больше, чем я думал. Все познакомились, начали общаться. Но важнее то, что PHP-митап послужил катализатором для других. Фронтендеров «задело», что у бэкенд-разработчиков есть свое мероприятие, а у них нет. Ко мне стали подходить люди из других отделов iSpring и советоваться, как организовать. Все давно хотели, но чего-то ждали. 

В итоге в январе и феврале оказались расписаны все выходные. Пришлось даже создать чат организаторов, чтобы договариваться о датах. Свои встречи провели мобильные разработчики iSpring, фронтендеры, DevOps, тестировщики. На мой второй митап по PHP приехал спикер из Skyeng. А один из участников первого мероприятия связался с компанией JetBrains, и те выделили призы для докладчиков и авторов лучших вопросов. На DevOps-митап мы тоже искали докладчиков в Казани и Нижнем Новгороде.

Первый PHP-митап послужил катализатором IT-движа в городе.
Первый PHP-митап послужил катализатором IT-движа в городе.

Идея в том, чтобы такие встречи не были активностью только одной компании. Хочется сделать сообщество разработчиков. И в названиях наших мероприятий есть Yola, а не название конкретной компании. Хотя компании — неотъемлемая часть сообщества, должны же разработчики где-то работать. Другие IT-компании Йошкар-Олы тоже подхватили инициативу. Даже набралось спикеров на два митапа про машинное обучение. Причем один из спикеров был из местного «Сернурского сырзавода». Да, у них есть IT-подразделение, и им есть, что рассказать. Но случился карантин. И часть митапов пришлось перенести в онлайн. 

От первоначальной задумки делать Go-митапы я не отказался. Один провели в онлайн-формате в конце апреля вместе с Искандером Шариповым из Казани при поддержке Avito. Из него получилось сделать мини-конференцию «Stay home and GOnline» с 8 докладчиками из iSpring, Google, VK, Avito и других компаний, которые выступали в два параллельных потока. Второй митап «Карантин, Go away!» организовывал почти полностью самостоятельно. Оказалось, что спикеры из других компаний довольно активно откликаются на предложение выступить. У нас были докладчики из МТС, Toggl, iSpring. Выступить согласился даже Алексей Палажченко из Percona — один из основателей российского Go-сообщества. Более того, я почти вышел на международный уровень. Ребята из JetBrains познакомили с Флорином Патаном (Florin Pățan), очень известным в Европе Go-разработчиком. Он согласился, но потом вспомнил, что в этот день у него годовщина с женой, и отказался.

Нам с Искандером нравится проводить митапы :)
Нам с Искандером нравится проводить митапы 🙂

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

Так, митаповский доклад моей коллеги из iSpring Юлии Николаевой про модульный PHP монолит вылился в ее участие в подкасте и статью на Хабре. Еще одного моего коллегу Андрея Овчинникова пригласили выступить на виртуальных PHP-митапах на 370 онлайн-зрителей. А меня позвали на Code Fest в Новосибирск, организаторы приняли мой доклад, но из-за карантина пока все отложилось.  

Советы по организации митапов

Если вы хотите развивать сообщество разработчиков в своем городе, то вот несколько советов из моего опыта.

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

Нужно ли гнаться за массовостью? Я понимаю, что в Йошкар-Оле вряд ли будут мероприятия на сотни человек, у нас нет столько людей. Но мне маленькие, уютные митапы нравятся больше. Одну из встреч мы проводили в баре, и такой формат всем понравился. Лучше собрать 5 человек, которые будут общаться друг с другом, чем 50, которые молча послушают, а потом встанут и уйдут. 

Доклад на митапе — не научная работа. Многие думают, что митапы это про инновации и какие-то крутые штуки. Но их в жизни бывает мало. В своих докладах люди просто делятся своим опытом, чтобы сэкономить время другим. Например, у нас ребята рассказывали про свой опыт выбора облачных провайдеров. Они потратили кучу времени на выбор, а потом за 20 минут все рассказали. И теперь все могут пользоваться их опытом. Всегда интересно слушать «выживших» — тех, кто столкнулся с какой-то проблемой, решил ее и теперь делится личным опытом. 

Где искать докладчиков? Люди из совершенно разных компаний готовы выступать на ваших мероприятиях. Главное — предложить им это. Когда я искал спикеров, то удивился, сколько людей соглашались. Да, некоторым были неудобны даты, но они хотели сотрудничать и просили звать их в следующий раз. И докладчики даже готовы приезжать к вам за свой счет.  Например, спикер из Skyeng приехал за свой счет, также готовы были приехать разработчики из JetBrains, Toggle, VK. Недавно вот договорились об удалённом докладе на следующий митап со спикером Brian Ketelsen из Microsoft, у него очень интересные выступления

Делайте предварительные прогоны. Да, я стараюсь со всеми докладчиками сделать предварительное выступление. И даже прошу несколько людей послушать. Почти всегда после предпрогона удается сделать доклад лучше. Конечно, придется подготовить доклад как минимум за неделю, а не в последнюю ночь, но это пойдет ему только на пользу. У большинства докладчиков есть общая проблема — почти никто не укладывается в тайминг.

Сложно ли организовать митап? Я люблю все организовывать. Недавно я даже организовал свадьбу другу, который не хотел праздновать. Конечно, на организацию придется потратить время. На онлайн Go-митап, который я организовывал самостоятельно, у меня ушло около 40 часов на поиск спикеров, предварительные прогоны, организационные моменты и так далее. Радует, что люди вокруг готовы помогать — с софтом для трансляций, с поиском докладчиков, с организацией, с красивыми картинками для заставки. При этом, на онлайн митап у меня ушло даже больше времени, чем на офлайн, потому что пришлось разбираться с streamyard (софт для трансляции) и spatial chat. 

Как помочь людям начать общаться? На самом деле, далеко не все разработчики интроверты, как принято думать. Самый верный способ спровоцировать общение — крутой доклад, который точно будут обсуждать люди в перерывах и на афтепати. Иногда я сам стараюсь запустить общение. Можно подойти к человеку, начать расспрашивать, чем он занимается и что в докладах ему было интересно. Люди видят, что кто-то что-то обсуждает, образуется кружок — и вот уже все общаются. На онлайн митапе после первого доклада не было ни одного вопроса. Люди стеснялись выйти в эфир. И мы придумали, что отправим каждому задавшему вопрос физическую открытку с логотипом мероприятия — люди заинтересовались и стали задавать вопросы. Плюс у нас есть традиция — на офлайн митапах мы дарим подарок за лучший вопрос и просим человека рассказать о себе. На одном из митапов в Казани оказалось, что лучший вопрос задал парень из Чебоксар. А рядом с ним сидел парень оттуда же, но они не знали друг друга. 

Организуйте интерактив. Кроме докладов на митапе у нас часто бывают викторины или другие движухи. Они дают дополнительный повод людям начать общаться, плюс это отличная возможность разыграть сувениры от компаний. Для онлайн митапов хорошо подходит mentimeter, правда он бесплатен только на 5 вопросов. В нём сразу отображаются варианты ответов, что важно ввиду задержки трансляции. Для офлайн митапов мы использовали kahoot. В целом выбор приложений для викторин большой. Для митапа в баре мы придумали игру — города названиями стандартных PHP функций. Не ограничивайте свою фантазию, это отличный способ сблизить сообщество 🙂

Одна из фишек наших митапов - забавные интервью со слушателями и спикерами
Одна из фишек наших митапов — забавные интервью со слушателями и спикерами

Собирайте обратную связь. Мы не сразу сообразили, что надо делать это. А потом стали спрашивать и про организацию, и про доклады, и просто советы. Объясните людям, что вы делаете для них митап, а все, что просите взамен, это оставить фидбек. И многие ответят. А вы получите инсайты, как улучшить мероприятие. Один раз мы попробовали до митапа спросить про интересующие темы, а потом устроили голосование. Это тоже хороший инструмент для общения с аудиторией.

Помощь вашего работодателя. Все первые митапы я проводил в офисе iSpring — там есть все условия для этого. Бюджет одного мероприятия — около 15-20 тысяч рублей: работа оператора, книжки в подарок докладчикам и лучшему вопросу, пицца и напитки в перерыве. Для компании это не такие большие деньги, и она готова помочь. Взамен мы выкладываем доклады на YouTube-канал iSpring Tech. Это не способ схантить людей. Все делается на чистом энтузиазме. Но я не вижу ничего плохого в том, чтобы продвигая бренд Йошкар-Олы среди разработчиков, продвигать и бренд компании, где я работаю. Уверен, что если вы попросите о поддержке свою компанию, то вам вряд ли откажут в помощи. Хотя, конечно, в идеале хочется сделать сообщество всего города, а не конкретной компании.

Небольшая фотозона на митапе :)
Небольшая фотозона на митапе 🙂

Мой опыт показал, что лучший способ начать делать митапы — это просто начать. И вокруг вас тут же будут появляться люди, которые готовы будут помочь — другие организаторы, докладчики, ваши коллеги. Только нужно не стесняться спрашивать. Начать не так сложно, а вот не заглохнуть после пары митапов сложнее. Нужно видение, зачем ты это делаешь. Для меня все очень просто. Я хочу, чтобы разработчики не искали легких путей и не уезжали в Москву, а активно участвовали в сообществе Йошкар-Олы. А вторая цель — чтобы люди узнавали, что в нашем городе есть крутые разработчики.

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

Разработка базы данных медиаплеера для организации музыки типа iTunes

Описание предметной области

Предметной областью является медиаплеер, аналог iTunes. Основными сущностями в данной базе данных выступает пользователь и сборник композиций.

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

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

Построение логической модели

Используя описание предметной области, построили логическую модель

image

Note

Для разрешения отношение многие-ко-многим между сущностями «USERS» и «TRACK LIST» была создана таблица «SUBSCRIPTION VALIDITY», которая включает в себя дату начала подписки и её конец.

Остальные сущности отображают сущности нашей предметной области.

Построение реляционной модели

Логическая модель была переведена в реляционную с использованием Oracle SQL Developer Data Modeler

image

Соответствие нормальным формам

Все таблицы данной модели соответствуют 1НФ, потому что все атрибуты имеют только единственное значение для каждой записи, что исключает множественность.

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

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

Все таблицы соответствуют НФБК, потому что детерминант всех функциональных зависимостей являются потенциальными ключами.

Пояснения функциональных зависимостей

image

Создание базы данных и приложения

База данных создается на локальном сервере phpMyAdmin. Результат работы программы представлен ниже:

image

Доступные страницы

Стартовое окно/ Окно входа

image

Основная страница приложения

image

Окно регистрации нового пользователя

image

Окно персональных данных пользователя

image

Личный кабинет пользователя

image

Тестирование программы

Проверка реакции приложения на ожидаемые входные данные и тестирование работоспособности кнопок

Проверим правильность реакции приложения на ожидаемые входные данные + работоспособность всех полей, кнопок и выпадающих списков.

Проверка корректности регистрации пользователя

image

Доказательство корректного создания нового пользователя

image

Проверка правильности работы кнопки добавления музыки в медиатеку пользователя

Добавление композиции «FALSE ALARM by THE WEEKND» в плейлист пользователя

image

Доказательство добавления композиции «FALSE ALARM by THE WEEKND» в плейлист пользователя

image

Композиция «FALSE ALARM by THE WEEKND» в БД «TRACK LIST» соответствует номеру 32

image

Как видно из таблицы «MY MUSIC», композиция с номером 32 действительно добавилась в плэйлист пользователя

image

Проверка правильности работы кнопки удаления музыки из медиатеки пользователя

Удаление композиции «FALSE ALARM by THE WEEKND» из плейлиста пользователя

image

Доказательство удаления композиции «FALSE ALARM by THE WEEKND» в плейлист пользователя

image

Как видно из таблицы «MY MUSIC», композиция с номером 32 действительно удалилась из плэйлиста пользователя

image

При этом композиция 32 никуда не пропала из таблицы «TRACK LIST»

image

Проверка правильности работы кнопки сортировки музыки по исполнителям

Сортировка музыки по исполнителю «MUSE»

image

Сортировка музыки по исполнителю «KASABIAN»

image

Проверка правильности работы кнопки удаления аккаунта пользователя из системы

Создадим в системе пользователя «ivanivanov@mail.ru»

image

Перейдем во вкладку с персональными данными этого пользователя

image

Теперь удалим данного пользователя при помощи кнопки «Delete user from system»

image

Как видно из таблицы «USERS», пользователь с номером 18 действительно удалился из списка зарегестрированых пользователей

Проверка реакции приложения на ошибочные входные данные

При вводе некорректных данных программа уведомляет об этом пользователя

Ошибка некорректного введения имени

image

Ошибка некорректного введения почты

image

Ошибка некорректного введения номера телефона

image

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

image

Ошибка возникающая при попытке введения учетных данных несуществующего пользователя

image

На ожидаемый ввод данных все работает. На отсутствие ввода и при неправильном вводе программа выдает предупреждение

Приложения

Приложение 1 – код для создания таблиц

Код для создания таблиц в Oracle Data Modeler

-- Generated by Oracle SQL Developer Data Modeler 19.4.0.350.1424 --   at:        2020-06-01 16:18:08 MSK --   site:      Oracle Database 11g --   type:      Oracle Database 11g  CREATE TABLE albums (     id            VARCHAR2(5) NOT NULL,     album_name    VARCHAR2(20) NOT NULL,     release_year  DATE NOT NULL,     genre         VARCHAR2(20) NOT NULL );  ALTER TABLE albums ADD CONSTRAINT albums_pk PRIMARY KEY ( id );  CREATE TABLE my_music (     user_id    VARCHAR2(5) NOT NULL,     my_msc_id  NUMBER NOT NULL );  CREATE UNIQUE INDEX my_music__idx ON     my_music (         user_id     ASC );  ALTER TABLE my_music ADD CONSTRAINT my_msc_pk PRIMARY KEY ( my_msc_id );  CREATE TABLE performers (     id              VARCHAR2(5) NOT NULL,     performer_name  VARCHAR2(30) NOT NULL );  ALTER TABLE performers ADD CONSTRAINT performers_pk PRIMARY KEY ( id );  CREATE TABLE subscription_validity (     "Start_Date/_Start_Time"  DATE NOT NULL,     "End_Date/_End_Time"      DATE NOT NULL,     user_id                   VARCHAR2(5) NOT NULL,     track_list_id             VARCHAR2(5) NOT NULL );  ALTER TABLE subscription_validity ADD CONSTRAINT subscription_validity_pk PRIMARY KEY ( user_id,                                                                                         track_list_id );  CREATE TABLE songs (     id                  VARCHAR2(5) NOT NULL,     track_name          VARCHAR2(50) NOT NULL,     genre               VARCHAR2(20) NOT NULL,     duration            TIMESTAMP NOT NULL,     albums_id           VARCHAR2(5) NOT NULL,     performers_id       VARCHAR2(5) NOT NULL,     my_music_my_msc_id  NUMBER NOT NULL );  ALTER TABLE track_list ADD CONSTRAINT track_list_pk PRIMARY KEY ( id );  CREATE TABLE "USER" (     id                  VARCHAR2(5) NOT NULL,     password            VARCHAR2(20) NOT NULL,     name                VARCHAR2(30) NOT NULL,     email               VARCHAR2(30) NOT NULL,     phone_number        VARCHAR2(12) NOT NULL,     country             VARCHAR2(20) NOT NULL,     balance             NUMBER(7, 2),     my_music_my_msc_id  NUMBER NOT NULL );  CREATE UNIQUE INDEX user__idx ON     "USER" (         my_music_my_msc_id     ASC );  ALTER TABLE "USER" ADD CONSTRAINT user_pk PRIMARY KEY ( id );  ALTER TABLE my_music     ADD CONSTRAINT my_music_user_fk FOREIGN KEY ( user_id )         REFERENCES "USER" ( id );  ALTER TABLE subscription_validity     ADD CONSTRAINT subs_val_track_list_fk FOREIGN KEY ( track_list_id )         REFERENCES track_list ( id );  ALTER TABLE subscription_validity     ADD CONSTRAINT subs_val_user_fk FOREIGN KEY ( user_id )         REFERENCES "USER" ( id );  ALTER TABLE track_list     ADD CONSTRAINT track_list_albums_fk FOREIGN KEY ( albums_id )         REFERENCES albums ( id );  ALTER TABLE track_list     ADD CONSTRAINT track_list_my_music_fk FOREIGN KEY ( my_music_my_msc_id )         REFERENCES my_music ( my_msc_id );  ALTER TABLE track_list     ADD CONSTRAINT track_list_performers_fk FOREIGN KEY ( performers_id )         REFERENCES performers ( id );  ALTER TABLE "USER"     ADD CONSTRAINT user_my_music_fk FOREIGN KEY ( my_music_my_msc_id )         REFERENCES my_music ( my_msc_id );  CREATE SEQUENCE my_msc_my_msc_id_seq START WITH 1 NOCACHE ORDER;  CREATE OR REPLACE TRIGGER my_msc_my_msc_id_trg BEFORE     INSERT ON my_music     FOR EACH ROW     WHEN ( new.my_msc_id IS NULL ) BEGIN     :new.my_msc_id := my_msc_my_msc_id_seq.nextval; END; /  -- Oracle SQL Developer Data Modeler Summary Report:  --  -- CREATE TABLE                             6 -- CREATE INDEX                             2 -- ALTER TABLE                             13 -- CREATE VIEW                              0 -- ALTER VIEW                               0 -- CREATE PACKAGE                           0 -- CREATE PACKAGE BODY                      0 -- CREATE PROCEDURE                         0 -- CREATE FUNCTION                          0 -- CREATE TRIGGER                           1 -- ALTER TRIGGER                            0 -- CREATE COLLECTION TYPE                   0 -- CREATE STRUCTURED TYPE                   0 -- CREATE STRUCTURED TYPE BODY              0 -- CREATE CLUSTER                           0 -- CREATE CONTEXT                           0 -- CREATE DATABASE                          0 -- CREATE DIMENSION                         0 -- CREATE DIRECTORY                         0 -- CREATE DISK GROUP                        0 -- CREATE ROLE                              0 -- CREATE ROLLBACK SEGMENT                  0 -- CREATE SEQUENCE                          1 -- CREATE MATERIALIZED VIEW                 0 -- CREATE MATERIALIZED VIEW LOG             0 -- CREATE SYNONYM                           0 -- CREATE TABLESPACE                        0 -- CREATE USER                              0 --  -- DROP TABLESPACE                          0 -- DROP DATABASE                            0 --  -- REDACTION POLICY                         0 --  -- ORDS DROP SCHEMA                         0 -- ORDS ENABLE SCHEMA                       0 -- ORDS ENABLE OBJECT                       0 --  -- ERRORS                                   0 -- WARNINGS                                 0  

Приложение 2 – код программы

Класс DataBase

package sample.database;  import javafx.event.ActionEvent; import sample.model.Performers; import sample.model.TrackList; import sample.model.User;  import java.sql.*; import java.util.ArrayList;  public class DataBase {      public static void main(String[] args) {         DataBase dataBase = new DataBase();         for (TrackList t:dataBase.allMusic()              ) {             System.out.println(t.toString());         }     }     //     public boolean loginIn(String email, String password) {         try {             Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/my_tunes", "root", "root");             Statement stmt = conn.createStatement();             // запрос на проверку пароля и логина (эмэил в качестве логина)             ResultSet rset = stmt.executeQuery("SELECT * FROM users u INNER JOIN subscription_validity s ON u.id_users=s.id_users where email = '" + email + "'&& password ='" + password + "'");             if (rset.next()) {                 User.setId(rset.getInt(1));                 User.setName(rset.getString(3));                 User.setEmail(rset.getString(4));                 User.setPhone(rset.getString(5));                 User.setCountry(rset.getString(6));                 User.setStartDate(rset.getString(8));                 User.setEndDate(rset.getString(9));                 return true;             }         } catch (SQLException e) {             e.printStackTrace();         }         return false;     }       public boolean emailCheck(String email) {         try {         	// подключение к db             Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/my_tunes", "root", "root");             // создание объекта Statement             Statement stmt = conn.createStatement();             // проверяем существует ли подобный эмэил в базе данных             ResultSet rset = stmt.executeQuery("select * from users where email = '" + email + "'");             return !rset.next();         } catch (SQLException e) {             e.printStackTrace();         }         return true;     }      public void registration(String name, String email, String phone, String password, String country, String startDate, String endDate) {         try {         	// подключение к db             Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/my_tunes", "root", "root");             // создание объекта Statement             Statement stmt = conn.createStatement();             // запрос на всавку данных в таблицу users нового пользователя             stmt.executeUpdate("insert into users (email,password,name,phone_num,contry) value ('" + email + "','" + password + "','" + name + "','" + phone + "','" + country + "')");             // запрос на всавку данных в таблицу subscription_validity             stmt.executeUpdate("insert into subscription_validity (Start_Date_Start_Time, End_Date_End_Time,id_users) value ('" + startDate + "','" + endDate + "', (select max(id_users) from users) )");             stmt.close();             conn.close();         } catch (SQLException e) {             e.printStackTrace();         }     }      // запрос для музыки     public ArrayList<TrackList> allMusic() {         ArrayList<TrackList> arrayList = new ArrayList<>();         try {             Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/my_tunes", "root", "root");              Statement stmt = conn.createStatement();             // запрос на вывод всех трэков с их автором и их альбомом             ResultSet rset = stmt.executeQuery("SELECT * FROM track_list t INNER JOIN performers p  ON p.id_performers=t.id_performers INNER JOIN albums a ON a.id_albums=t.id_albums");             while (rset.next()) {                 arrayList.add(new TrackList(rset.getInt(1), rset.getString(2), rset.getString(10), rset.getString(8), rset.getString(3), rset.getString(4)));             }             rset.close();             stmt.close();             conn.close();         } catch (SQLException throwables) {             throwables.printStackTrace();         }         return arrayList;     }      // запрос для музыки     public ArrayList<TrackList> myMusic(int id) {         ArrayList<TrackList> arrayList = new ArrayList<>();         try {             Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/my_tunes", "root", "root");              Statement stmt = conn.createStatement();             // запрос на вывод всех трэков с их автором и их альбомом             ResultSet rset = stmt.executeQuery("SELECT m.id_track_list, t.track_name, " +                     "a.album_name, p.performer_name, t.genre, t.duration FROM my_music m INNER JOIN " +                     "track_list t ON t.id_track_list=m.id_track_list INNER JOIN performers p ON " +                     "p.id_performers=t.id_performers INNER JOIN albums a ON a.id_albums=t.id_albums WHERE m.id_users=" + id);             while (rset.next()) {                 arrayList.add(new TrackList(rset.getInt(1), rset.getString(2), rset.getString(3), rset.getString(4), rset.getString(5), rset.getString(6)));             }             rset.close();             stmt.close();             conn.close();         } catch (SQLException throwables) {             throwables.printStackTrace();         }         return arrayList;     }      public void addMyMusic(int idUser, int idTrackList) {         try {             Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/my_tunes", "root", "root");             Statement stmt = conn.createStatement();             ResultSet rset = stmt.executeQuery("SELECT * FROM my_music WHERE " +                     "id_users = " + idUser + " && id_track_list = " + idTrackList);             // сюда передаем id user, которому будет добавлять музыку             // проверяем добавляли мы уже такое или нет             if (!rset.next())                 stmt.executeUpdate("INSERT INTO my_music (id_users, id_track_list) VALUE ("+ idUser +", "+  idTrackList +")");             rset.close();             stmt.close();             conn.close();             // перед тем как добавить трэк проверяем есть ли тако         } catch (SQLException e){             e.printStackTrace();         }     }      public void deleteMyMusic(int idUser, int idTrackList) {         try {             Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/my_tunes", "root", "root");             Statement stmt = conn.createStatement();             stmt.executeUpdate("DELETE FROM my_music WHERE id_users =" + idUser + "&& id_track_list =" + idTrackList);             stmt.close();             conn.close();         } catch (SQLException e){             e.printStackTrace();         }     }     // Запрос на сортировку по исполнителям     public ArrayList<Performers> performers(){         ArrayList<Performers> arrayList = new ArrayList();         try {             Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/my_tunes", "root", "root");             Statement stmt = conn.createStatement();             ResultSet rset = stmt.executeQuery("SELECT * FROM performers");             while(rset.next()){                 arrayList.add(new Performers(rset.getInt(1),rset.getString(2)));             }             rset.close();             stmt.close();             conn.close();         } catch (SQLException e){             e.printStackTrace();         }         return arrayList;     }     // запрос на вывод музыки с исполнителем     public ArrayList<TrackList> performerMusic(int id) {         ArrayList<TrackList> arrayList = new ArrayList<>();         try {             Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/my_tunes", "root", "root");              Statement stmt = conn.createStatement();             // запрос на вывод всех трэков с их автором и их альбомом             ResultSet rset = stmt.executeQuery("SELECT * FROM track_list t INNER JOIN performers p  ON p.id_performers=t.id_performers INNER JOIN albums a ON a.id_albums=t.id_albums WHERE p.id_performers = " + id);             while (rset.next()) {                 arrayList.add(new TrackList(rset.getInt(1), rset.getString(2), rset.getString(10), rset.getString(8), rset.getString(3), rset.getString(4)));             }             rset.close();             stmt.close();             conn.close();         } catch (SQLException throwables) {             throwables.printStackTrace();         }         return arrayList;     }     // запрос на удаление пользователя     public void deleteUser(int idUser) {         try {             Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/my_tunes", "root", "root");             Statement stmt = conn.createStatement();             stmt.executeUpdate("DELETE FROM users WHERE id_users =" + idUser);             stmt.close();             conn.close();         } catch (SQLException e){             e.printStackTrace();         }     } } 

Класс Main

 package sample;  import javafx.application.Application; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.stage.Modality; import javafx.stage.Stage;  public class Main extends Application {      @Override     public void start(Stage primaryStage) throws Exception{         Parent root = FXMLLoader.load(getClass().getResource("/sample/fxml/login.fxml"));         Stage newStage = new Stage();         newStage.setScene(new Scene(root));         newStage.initModality(Modality.NONE);         newStage.show();     }      public static void main(String[] args) {         launch(args);     } } 

Класс Music

 package sample;  import javafx.application.Application; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.fxml.Initializable; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.ComboBox; import javafx.scene.control.ListView; import javafx.stage.Modality; import javafx.stage.Stage; import sample.database.DataBase; import sample.model.Performers; import sample.model.TrackList; import sample.model.User;  import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.ResourceBundle;  public class Music implements Initializable {     @FXML     public ListView<TrackList> allMusic;     @FXML     public ComboBox comboBox;     @FXML     public Button myMusic;      DataBase dataBase = new DataBase();     ArrayList<TrackList> arrayList;      TrackList trackList = null;      public void addMyMusic(ActionEvent actionEvent) {     	// добавление музыки в "мой плейлист"         if(trackList != null)             dataBase.addMyMusic(User.getId(),trackList.getId());     }      @Override     public void initialize(URL location, ResourceBundle resources) {         arrayList = dataBase.allMusic();         ObservableList<TrackList> observableList = FXCollections.observableList(arrayList);          allMusic.setItems(observableList);         allMusic.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<TrackList>(){              @Override             public void changed(ObservableValue<? extends TrackList> observable, TrackList oldValue, TrackList newValue) {                 trackList = newValue;             }         });          for (Performers p:dataBase.performers()         ) {             comboBox.getItems().add(p);         }          comboBox.getItems().add(new Performers(0,"All"));         comboBox.setValue(new Performers(0,"All"));         comboBox.valueProperty().addListener(new ChangeListener<Performers>() {              @Override             public void changed(ObservableValue<? extends Performers> observable, Performers oldValue, Performers newValue) {                 if(newValue.getId() == 0) {                     arrayList = dataBase.allMusic();                     ObservableList<TrackList> observableList = FXCollections.observableList(arrayList);                     allMusic.setItems(observableList);                 } else{                     arrayList = dataBase.performerMusic(newValue.getId());                     ObservableList<TrackList> observableList = FXCollections.observableList(arrayList);                     allMusic.setItems(observableList);                 }             }         });     }      public void myMusic(ActionEvent actionEvent) {         Stage stage = (Stage) myMusic.getScene().getWindow();         stage.close();         try {             Parent root = FXMLLoader.load(getClass().getResource("/sample/fxml/mymusic.fxml"));             Stage newStage = new Stage();             newStage.setScene(new Scene(root));             newStage.initModality(Modality.NONE);             newStage.show();         } catch (IOException e) {             e.printStackTrace();         }     } } 

Класс MyMusic

 package sample;  import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.fxml.Initializable; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.ListView; import javafx.stage.Modality; import javafx.stage.Stage; import sample.database.DataBase; import sample.model.TrackList; import sample.model.User;  import javax.jws.soap.SOAPBinding; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.ResourceBundle;  public class MyMusic implements Initializable {     @FXML     public ListView allMusic;     @FXML     public Button back;     @FXML     public Button userData;     DataBase dataBase = new DataBase();     ArrayList<TrackList> arrayList;     TrackList trackList = null;      public void deleteMyMusic(ActionEvent actionEvent) {         if(trackList != null)             dataBase.deleteMyMusic(User.getId(),trackList.getId());          arrayList = dataBase.myMusic(User.getId());         ObservableList<TrackList> observableList = FXCollections.observableList(arrayList);          allMusic.setItems(observableList);     }      @Override     public void initialize(URL location, ResourceBundle resources) {         arrayList = dataBase.myMusic(User.getId());         ObservableList<TrackList> observableList = FXCollections.observableList(arrayList);          allMusic.setItems(observableList);         allMusic.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<TrackList>(){              @Override             public void changed(ObservableValue<? extends TrackList> observable, TrackList oldValue, TrackList newValue) {                 trackList = newValue;             }         });     }      public void userData(ActionEvent actionEvent) {         Stage stage = (Stage) userData.getScene().getWindow();         stage.close();         try {             Parent root = FXMLLoader.load(getClass().getResource("/sample/fxml/userdata.fxml"));             Stage newStage = new Stage();             newStage.setScene(new Scene(root));             newStage.initModality(Modality.NONE);             newStage.show();         } catch (IOException e) {             e.printStackTrace();         }     }      public void back(ActionEvent actionEvent) {         Stage stage = (Stage) back.getScene().getWindow();         stage.close();         try {             Parent root = FXMLLoader.load(getClass().getResource("/sample/fxml/music.fxml"));             Stage newStage = new Stage();             newStage.setScene(new Scene(root));             newStage.initModality(Modality.NONE);             newStage.show();         } catch (IOException e) {             e.printStackTrace();         }     } } 

Класс Registration

 package sample;  import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.PasswordField; import javafx.scene.control.TextField; import javafx.stage.Stage; import sample.database.DataBase;   public class Registration {     @FXML     public Label error;     @FXML     public Button reg;     @FXML     public TextField phone;     @FXML     public PasswordField password;     @FXML     public PasswordField repeatPassword;     @FXML     public TextField name;     @FXML     public TextField email;     @FXML     public TextField endDate;     @FXML     public TextField startDate;     @FXML     public TextField country;      public void registration(ActionEvent actionEvent) {         DataBase dataBase = new DataBase();         // String name,String email,String phone, String password, String country, String startDate, String endDate         if(name.getText().matches("[a-zA-ZРђ-РЇР°-СЏ]{3,}")){                 if(email.getText().matches("[A-Za-z0-9]{3,}[@][A-Za-z0-9]{3,}[.][A-Za-z0-9]{2,}")){                     if(phone.getText().matches("[+][7][0-9]{10}") || phone.getText().matches("[8][0-9]{10}")){                         if(dataBase.emailCheck(email.getText())){                             if(password.getText().equals(repeatPassword.getText())){                                 dataBase.registration(name.getText(),email.getText(),                                         phone.getText(),password.getText(),country.getText(), startDate.getText(), endDate.getText());                                 Stage stage = (Stage)reg.getScene().getWindow();                                 stage.close();                             }else{                                 error.setText("Пароли не совпадают");                             }                         }else{                             error.setText("Данный логин существует");                         }                     }else{                         error.setText("Введите телефон в формате +7********** или 8**********");                     }                 }else{                     error.setText("Введите emeail в формате ***@***.**");                 }         }else{             error.setText("Корректно введите имя");         }     } } 

Класс Userdata

 package sample;  import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.fxml.Initializable; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.stage.Modality; import javafx.stage.Stage; import sample.database.DataBase; import sample.model.User;  import java.io.IOException; import java.net.URL; import java.util.ResourceBundle;  public class Userdata implements Initializable {     @FXML     public Label name;     @FXML     public Label email;     @FXML     public Label phone;     @FXML     public Label contry;     @FXML     public Label startDate;     @FXML     public Label endDate;     @FXML     public Button back;     @FXML     public Button delete;      @Override     public void initialize(URL location, ResourceBundle resources) {         name.setText(User.getName());         email.setText(User.getEmail());         phone.setText(User.getPhone());         contry.setText(User.getCountry());         startDate.setText(User.getStartDate());         endDate.setText(User.getEndDate());     }      public void back(ActionEvent actionEvent) {         Stage stage = (Stage) back.getScene().getWindow();         stage.close();         try {             Parent root = FXMLLoader.load(getClass().getResource("/sample/fxml/mymusic.fxml"));             Stage newStage = new Stage();             newStage.setScene(new Scene(root));             newStage.initModality(Modality.NONE);             newStage.show();         } catch (IOException e) {             e.printStackTrace();         }     }      public void delete(ActionEvent actionEvent) {         DataBase dataBase = new DataBase();         dataBase.deleteUser(User.getId());          Stage stage = (Stage) delete.getScene().getWindow();         stage.close();         try {             Parent root = FXMLLoader.load(getClass().getResource("/sample/fxml/login.fxml"));             Stage newStage = new Stage();             newStage.setScene(new Scene(root));             newStage.initModality(Modality.NONE);             newStage.show();         } catch (IOException e) {             e.printStackTrace();         }     } } 

Класс Performers

 package sample.model;  public class Performers {     private int id;     private String name;      public Performers(int id, String name) {         this.id = id;         this.name = name;     }      @Override     public String toString() {         return name;     }      public int getId() {         return id;     }      public void setId(int id) {         this.id = id;     }      public String getName() {         return name;     }      public void setName(String name) {         this.name = name;     } } 

Класс TrackList

 package sample.model;  public class TrackList {     private int id;     private String trackName;     private String albumName;     private String performerName;     private String genre;     private String duration;      public TrackList(int id, String trackName, String albumName, String performerName, String genre, String duration) {         this.id = id;         this.trackName = trackName;         this.albumName = albumName;         this.performerName = performerName;         this.genre = genre;         this.duration = duration;     }      public int getId() {         return id;     }      public void setId(int id) {         this.id = id;     }      public String getTrackName() {         return trackName;     }      public void setTrackName(String trackName) {         this.trackName = trackName;     }      public String getAlbumName() {         return albumName;     }      public void setAlbumName(String albumName) {         this.albumName = albumName;     }      public String getPerformerName() {         return performerName;     }      public void setPerformerName(String performerName) {         this.performerName = performerName;     }      public String getGenre() {         return genre;     }      public void setGenre(String genre) {         this.genre = genre;     }      public String getDuration() {         return duration;     }      public void setDuration(String duration) {         this.duration = duration;     }      @Override     public String toString() {         return                 trackName +                 " by " + performerName  +                 " from album - " + albumName +                 " (" + genre + ") " +                 ", " + duration;     }   } 

Класс User

 package sample.model;  public class User {     private static int id;     private static String name;     private static  String email;     private static String phone;     private static String country;      private static String startDate;     private static String endDate;      public static int getId() {         return id;     }      public static void setId(int id) {         User.id = id;     }      public static String getName() {         return name;     }      public static void setName(String name) {         User.name = name;     }      public static String getEmail() {         return email;     }      public static void setEmail(String email) {         User.email = email;     }      public static String getPhone() {         return phone;     }      public static void setPhone(String phone) {         User.phone = phone;     }      public static String getCountry() {         return country;     }      public static void setCountry(String country) {         User.country = country;     }      public static String getStartDate() {         return startDate;     }      public static void setStartDate(String startDate) {         User.startDate = startDate;     }      public static String getEndDate() {         return endDate;     }      public static void setEndDate(String endDate) {         User.endDate = endDate;     } } 

Класс Login

 package sample;  import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.PasswordField; import javafx.scene.control.TextField; import javafx.stage.Modality; import javafx.stage.Stage; import sample.database.DataBase;  import java.io.IOException;  public class Login {     @FXML     public Button loginIn;     @FXML     public Button registration;     @FXML     public TextField email;     @FXML     public PasswordField password;     @FXML     public Label error;      public void loginIn(ActionEvent actionEvent) {         DataBase dataBase = new DataBase();         if(email.getText() != null ){             if(password.getText() != null){                 if(dataBase.loginIn(email.getText(), password.getText())){                     Stage stage = (Stage) loginIn.getScene().getWindow();                     stage.close();                     try {                         Parent root = FXMLLoader.load(getClass().getResource("/sample/fxml/music.fxml"));                         Stage newStage = new Stage();                         newStage.setScene(new Scene(root));                         newStage.initModality(Modality.NONE);                         newStage.show();                     } catch (IOException e) {                         e.printStackTrace();                     }                 } else {                     error.setText("This Login or Password does not exist");                 }             } else {                 error.setText("Please, enter your Password: ");             }         } else {             error.setText("Please, enter your Login: ");         }     }      public void registration(ActionEvent actionEvent) {         try {             Parent root = FXMLLoader.load(getClass().getResource("/sample/fxml/registration.fxml"));             Stage newStage = new Stage();             newStage.setScene(new Scene(root));             newStage.initModality(Modality.NONE);             newStage.show();         } catch (IOException e) {             e.printStackTrace();         }     } } 

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

Защита RDP-подключения к VDS/VPS в эпоху «заслуженного» киберпанка

Пандемия вируса COVID-19 радикально изменила модель работы персонала множества организаций в добровольно-принудительном порядке, «наградив» большую его часть статусом «дистанционный», а кое-кого, даже «удалённый работник».

Если до «мега-эпидемии» сотрудники выполняли свои трудовые обязанности из офиса, используя подконтрольную IT-отделу компании корпоративную инфраструктуру, то во время самоизоляции, «львиная» доля офисной работы стала выполняться с домашних устройств с использованием протокола удалённого рабочего стола (RDP). Популярного, как сама ОС от MS, но, как свидетельствует список уязвимостей, не самого безопасного протокола. Как защитить свой RDP от посягательств извне, мы далее и поговорим.

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

RDP наоборот

В 2018 году, в процессе исследований безопасности протокола удалённого  рабочего стола, специалисты Check Point Research  обнаружили множественные уязвимости в трёх популярных клиентах, предназначенных для работы с ним:

  • RDP-клиент от Microsoft / Mstsc.exe
  • rdesktop 
  • FreeRDP

Всего было обнаружено 25 уязвимостей, среди  которых были и критические, в том числе и те, что позволяют злоумышленнику изменить обычное направление связи и атаковать клиентский компьютер, то есть осуществить атаку с помощью обратного RDP-соединения (reverse RDP-attack).

В RDP-клиенте от Microsoft эта уязвимость «таилась» в общем буфере обмена между клиентом и сервером. Которая, при использовании буфера в процессе соединения, позволяла серверу с эксплойтом «включать» собственные файлы в инициированный пользователем процесс обмена.  А функционалу атаки обхода пути (path traversal attack) — определять «конечным пунктом назначения» для файла любое место в клиентском компьютере. Например, без лишних уведомлений отправить исполняемый файл (программу-майнер, шифровальщик) в папку автозагрузки компьютера пользователя, для его запуска при следующей перезагрузке системы. 

Демонстрация уязвимости от Check Point Research

О факте чего и было сообщено компании Microsoft (MSRC) 16 октября 2018 года. В ответ на что,17 декабря 2018 года адресат ответил:

“Thank you for your submission. We determined your finding is valid but does not meet our bar for servicing. For more information, please see the Microsoft Security Servicing Criteria for Windows (https://aka.ms/windowscriteria).”

«Спасибо за подачу заявки. Мы определили, что ваша находка действительна, но не соответствует нашему уровню обслуживания. Дополнительные сведения см. в разделе Критерии обслуживания Microsoft для Windows (https://aka.ms/windowscriteria)».

В результате чего данная уязвимость не получила своего ID и патча для её исправления. источник

ГипеРДП

После публикации информации об этой уязвимости, сотрудники Check Point Research стали получать множество комментариев и вопросов, один из которых вызвал неподдельный интерес и побудил вернуться к дальнейшему исследованию и посмотреть на уязвимость под «другим углом», а именно: может ли уязвимость RDP-клиента Microsoft быть использована в Microsoft Hyper-V?

В результате дальнейшего изучения выяснилось, что в основе GUI для управления ВМ — Hyper-V manager, негласно используется технологии RDP, в расширенных сеансах которого, так же как и в свойствах удалённого рабочего стола есть возможность включения общего буфера обмена. И как следствие — присутствует та же самая уязвимость!

Демонстрация уязвимости в Hyper-V от Check Point Research

Об этом сотрудники Check Point Research рассказали в своем блоге, а также на конференции Black Hat USA 2019.

Презентация уязвимости на Black Hat USA 2019

Ну и, конечно же, сообщили в MSRC. На этот раз Microsoft завела тикет и в октябре 2019 года выпустила патч для исправления этой уязвимости, получившей  ID: CVE-2019-0887. источник

Неисповедимы пути

После анализа эффективности патча, когда эксперты Check Point Research собрались уже было присвоить уязвимости статус «закрыта», к ним обратился пользователь Mac OS RDP Client, с информацией об особенностях поведения клиента после установки исправления от MS.

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

В феврале 2020 года Microsoft собралась с силами и выпустила патч для исправления и этой уязвимости CVE-2020-0655, который, по словам экспертов Check Point Research, в полной мере так не решил проблему атаки обхода пути (path traversal attack), в частности, для Windows API. Оповещённая об этом «косячке» компания Microsoft пока эту ситуацию никак не прокомментировала. источник

И, что…

Как может заметить проницательный читатель, для удачного проведения атаки такого типа необходим скомпрометированный RDP-сервер. А «достать» его можно тремя путями:

  • Создать свой и выдать его за легитимный
  • Получить доступ к существующему и скомпрометировать его
  • Гибридный

Относительно безопасным, оптимальным по затратам и скорости провижининга вариантом в сегодняшних реалиях будет — «завладеть» арендуемым у сервис-провайдера облачным или VPS/VDS — сервером. И тому есть три основные причины:

  • Каждый арендуемый виртуальный сервер под управлением Windows — это уже RDP-сервер по-умолчанию
  • Каждый арендуемый виртуальный сервер под управлением Windows, как правило, уже имеет RDP-аккаунт с админскими правами и единым логином для всех пользователей. 
  • Как правило, RDP-доступ к виртуальному серверу осуществляется через доступный на публичном IP-адресе TCP-порт: 3389.

Собственно этот — «торчащий наружу» порт, наверно, та, самая «красная тряпка» для кибер-злоумышленника, желающего завладеть каким-нибудь виртуальным сервером, подобрав пароль к нему методом простого автоматизированного перебора. Или как его ещё называют брутфорс-атакой.

Для справки: Пример роста количества атак семейства Bruteforce.Generic.RDP, февраль — апрель 2020 г. от Лаборатории Касперского.

источник

… дальше

Для того чтобы не стать жертвой вышеописанных ситуаций, можно, конечно, менять параметры учётных записей и стандартные RDP-порты, блокировать неавторизованные попытки подключения с помощью брандмауэра Windows или сторонних программ типа IPBAN, использовать шифрование и сертификаты, и многое-многое другое. Но для меня самый простым и быстрым способом защиты RDP-подключения к вновь создаваемому серверу является разрешения RDP-доступа к нему только с узлов из доверенной сети.

В RUVDS для этого требуется три простых шага:

  • Объединить виртуальный сервер с компьютерами, которым необходим доступ к нему по RDP, виртуальной частной сетью. Я использую — ZeroTier 
  • Настроить нативный файрвол виртуального сервера:
    В настройках сервера во вкладке Сети кликаем настроить файрвол для публичных адресов.

    image

    И начинаем создавать правила:
    Первое — запрещаем все внешние соединения
    Второе — разрешаем соединения виртуальной сети. В данном случае — ZeroTier.
    Третье — разрешаем RDP-соединение только по адресу виртуального сервера в виртуальной сети. Принимаем изменения.

  • Установить успешное RDP-соединение по IP-адресу сервера внутри виртуальной сети согласно этой инструкции.

На этом хочу откланяться и советую следить за «здоровьем» RDP-подключения вашего виртуального сервера. Ибо, с момента CVE-2020-0655, появилось ещё три уязвимости с функционалом reverse RDP-attack.

ссылка на оригинал статьи https://habr.com/ru/company/ruvds/blog/516814/

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

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

Лошади, образцовые пролетарии и ядерные взрывы: что пытались фотографировать создатели захвата движения?

В 1878 губернатор Калифорнии, основатель Стэнфордского университета и любитель верховой езды Леланд Стэнфорд заключил со своими приятелями пари. Он утверждал, что бегущая галопом лошадь в определенный момент отрывает от земли все четыре ноги. Приятели не соглашались, а визуально доказать или подтвердить предположение было невозможно. Тогда Стэнфорд пригласил известного фотографа и исследователя движений животных Эдварда Мейбриджа разрешить спор. На ферме Леланда в Пало Альто был построен специальный «фотодром» — загон, состоящий из белой стены и направленных на нее с противоположной стены фотокамер. Поперек дорожки загона были протянуты веревки, привязанные к затворам камер. Когда лошадь переходила на галоп, наездник направлял её в фотодром, ноги животного задевали веревки, срабатывали затворы и появлялась серия снимков.

Так появилась хронофотография — первая технология захвата движения, которая помогла разрешить спор на 25 тыс. долларов.


Стэнфорд доказал свою правоту: в галопе лошадь действительно отрывает все четыре ноги от земли, но в этот момент они поджаты под туловище, а не распростерты вперед и назад, как это часто изображали художники разных эпох. Это маленькое открытие вызвало фурор среди искусствоведов и художников рубежа XIX-XX столетия. Кстати, для съемки использовали только черных лошадей — их движения отчетливее фиксируются на белом фоне фотодрома. Источник: Eadweard Muybridge / Wikimedia Commons

Но технология захвата движения была интересна не только возможностью выиграть пари, и впоследствии появился не один аппарат, основанный на принципе хронофотографии. К примеру, фотограф Арнольд Лонд изобрел хронофотограф с 12 объективами в 1891 году, чтобы запечатлевать мимику пациентов невролога Жана Шарко. С тех пор и до наших дней основной и наиболее сложной задачей было воспроизведение захваченных действий.

Первыми этой возможностью заинтересовались мультипликаторы. В первом десятилетии XX века началась эпоха мультфильмов, которые были либо кукольными, либо рисованными. Художники стремились добиться максимального правдоподобия движений рисованных персонажей. Это получилось у американского изобретателя Макса Фляйшера, который вместе с братом Дэйвом изобрел в 1914 году ротоскопирование («фотоперекладку»). Сначала на пленку снимали реальных актёров. Затем эта запись покадрово увеличивалась, проецировалась и воспроизводилась на стеклянный просвет. Художник прорисовывал каждый кадр на кальке. В результате получался новый фильм с реалистично двигающимся персонажем.

Первым мультперсонажем, созданным технологией фотоперекладки, стал клоун, которого сыграл брат Макса Флейшера Дэйв. Впоследствии вышел короткометражка Tantalizing Fly («Утомленный мухой») про борьбу художника и мешающей ему работать мухи. Художника сыграл сам Фляйшер. Методом фотоперекладки создали «Золушку», «Белоснежку и 7 гномов», «Все псы попадают в рай», советские «Цветик-семицветик», «Каштанку», «Ночь перед рождеством» и др. Источник: Mohamed El Amine CHRAIBI / YouTube

Реалистичное воспроизведение движений посредством их видеозахвата заинтересовало в те годы ученых Советской России, увлеченной научной организацией труда. Если изучить досконально движения лучших представителей пролетариата, то можно научить и других рабочих — такова была логика советского руководства и ученых. Этой темой занимался созданный в Москве Центральный институт труда (ЦИТ), а курировал её Николай Бернштейн — основатель научного направления биомеханики, изучающей движение человека. В своей лаборатории Бернштейн проводил циклограмметрические исследования: испытуемого одевали в специальный костюм, состоящий из десятков небольшим ламп, которые играли роль маркеров. Затем с помощью рапидной киносъемки (100-200 кадров в секунду) получали циклограмму. Погрешность измерений положений движущегося или бегущего человека составляла всего 0,5 мм.

За годы работы Бернштейн изучил движения спортсменов, рабочих и музыкантов, что помогло улучшить результаты в соревнованиях и разработать методики обучения в разных областях. Несмотря на опалу в период борьбы с «космополитами», Бернштейн еще успел дать рекомендации по адаптации космонавтов к состоянию невесомости. Источник: Thomas Oger / YouTube

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

Это задумку развивал американец Ли Харрисон III, который экспериментировал с аналоговыми микросхемами и электронно-лучевыми трубками. В 1959 году он создал костюм со встроенными потенциометрами (регулируемыми резисторами) и смог записывать и анимировать движения актера в режиме реального времени на электронно-лучевой трубке. Хотя это была примитивная установка — анимированный актер выглядел на экране как светящийся столб, — это был первый случай захвата движения в реальном времени.

Параллельно пробивал себе дорогу фотооптический метод, когда захват движения достигался путем фотографирования объекта на высокой скорости или с разных углов обзора. Большой вклад в это внес Гарольд Эджертон, изобретатель рапатронической камеры, созданной в 1940-х. Она способна записывать неподвижное изображение со временем экспозиции всего 10 наносекунд. Благодаря этому устройству ученый смог запечатлеть очень быстрые события — от всплеска воды из-за упавшей капли до ядерного взрыва.


Момент взрыва ядерного заряда спустя доли секунды после детонации, запечатлённый рапатронической камерой. Источник: Federal Government of the United States / Wikimedia Commons

К концу XX века сформировались две основные технологии захвата движения, которые условно называют маркерной и безмаркерной. В первом случае захват движения происходит с помощью размещенных на теле человека маркеров или датчиков. Они могут быть разными: инфракрасными, магнитными, гироскопическими. Инфракрасные могут либо отражать (пассивные), либо излучать (активные) свет, магнитные искажают магнитный поток, и ресивер волн определяет их положение в пространстве, гироскопический датчик также передает информацию об изменении положения тела в пространстве.

Безмаркерные системы основаны на оптическом захвате. В помещении монтируется множество камер, которые под разными углами ведут сьемку. Затем полученные изображения сводятся в 3D-модель. Еще одна разновидность — экзоскелет, который крепится к телу и создает анимационную модель, фиксируя движения.


Если модель захваченного персонажа впоследствии необходимо поместить в виртуальную среду, то его снимают в зеленой комнате (хромакей). Зелёный цвет очень контрастирует с цветами человеческого тела, поэтому компьютер лучше справляется с задачей «вырезания» зеленых пикселей из кадров. Источник: Toshiba

Захвата движения в основном используются в кино и видеоиграх. Однако с каждым годом эта технология всё чаще применяется вне развлекательной индустрии — на производстве, в медицине, сохранении культурного наследия, спорте и военном деле.

Повторяй за мной: как Toshiba учит работать напильником с помощью захвата движения?

Можно бесконечно смотреть на огонь, воду и на то, как кто-то работает. А иногда от этого можно получать пользу. Мы в Toshiba догадались об этом давно, поэтому в нашем Корпоративном центре производственного проектирования (Corporate Manufacturing Engineering Center) есть специалист по захвату движения Хироаки Накамура. С недавних пор он с увлечением следит за тем, как работают участники Национального чемпионата профессионалов — специального конкурса для умельцев в разных областях — от выпечки до сварки.

В 2018 году молодой рабочий одного из предприятий Toshiba Харуки Окабе решил поучаствовать в этом соревновании, конкретно — в сборке небольшого устройства. При его сборке конкурсанты используют старый добрый напильник для финальной обработки некоторых деталей. Считается, что первоклассные специалисты в этом деле могут достигать точности обработки в 0,001 мм или меньше, иначе устройство работать не будет. В этом наш конкурсант как раз и оказался слаб и упустил победу в конкурсе.

В наставники молодому специалисту Toshiba командировала 66-летнего инструктора Тацуо Мацуи, который более 50 лет работал напильником на предприятиях компании. Однако не каждый носитель уникальных навыков рождается хорошим педагогом, способным их передать. Он пояснил, что проблема в позе конкурсанта за станком, но дальше дело не продвинулось. Тогда мы облачили обоих в костюмы для захвата движения. Они «сшиты» по маркерному принципу, то есть для фиксации движений используются датчики. В этом случае — акселерометры, примерно такие же, как в смартфонах. Это гораздо дешевле многочисленных камер.

Когда мы сравнивали их данные, мы заметили одно существенное различие в том, как они балансируют на ногах.


Вертикальная ось — это сила противодействия опоры [N], а горизонтальная — время [S]. Поскольку по сравнению с опытным специалистом центр тяжести молодого рабочего больше сконцентрирован на выставленной вперед ноге, между его синей и зеленой кривыми большой разрыв. (Окабе, молодой рабочий, левша, а Мацуи, учитель, правша, поэтому на диаграммах линии обращены). Источник: Toshiba

Выяснилось, что центр тяжести молодого рабочего сильно смещен вперед, не так, как у опытного специалиста. Из-за этого конкурсант быстро устает при работе с напильником, и из-за этого выходит брак. Поняв свою ошибку, Окабе сумел улучшить качество работы напильником. И взял бронзу на Национальном чемпионате профессионалов Японии. Возможно, по записям работы напильником Мацуи будут учится наши потомки. А ещё мы можем сохранить для них другой вид исчезающего искусства — танцы.

Поймать Терпсихору: как захват движения используется в сохранении искусства танца?

Все наверняка слышали о списке Всемирного наследия ЮНЕСКО, куда входят более 1 000 природных и рукотворных объектов, которые человечество стремится сохранить и передать будущим поколениям. В начале 2000-х многие задумались о том, как сохранить то, что не построено руками и не создано природой, — пение, обряды, театральные представления, ремёсла. Так появилась концепция нематериального культурного наследия (НКН), которое с 2003 также накапливается и сохраняется. А технология захвата движения помогает сохранить один из главных объектов НКН — танцы.

Несколько проектов по всему миру занимаются оцифровкой танцев народов мира (Wholedance, i-Treasures, AniAge и другие). Чаще всего при записи танцев используются активные и пассивные датчики, которые размещаются на теле танцора.

Система захвата движения с активными датчиками используют светодиоды, которые излучают собственный свет. К примеру, система захвата движения Phasespace Impulse X2 состоит из восьми камер, способных фиксировать движение с помощью модулированных светодиодов. Танцор надевает костюм с 38 датчиками и активными светодиодами и начинает исполнять танец.

В проекте WholoDance экспериментировали с интеграцией Microsoft HoloLens в качестве инструмента визуализации. Потоковая передача данных из Autodesk MotionBuilder по беспроводной связи на гарнитуру позволяет танцорам и хореографам видеть голограммы в реальном времени. Источник: Jasper Brekelmans / YouTube

В пассивных системах захвата датчики не излучают сигнал, а только отражают его с помощью специальных материалов, из которых изготовлен костюм. Эта технология обеспечивает бóльшую свободу действия танцора, позволяет ему совершать резкие движения, выполнять акробатические элементы и при этом не снижает точность и скорость фиксации. Сейчас ученые рассчитывают интегрировать маркерные технологии захвата движения и 3D-съемку, так как в танце участвует мимика и костюм танцора.

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

Узнáю по походке: как изучение походки помогает врачам?

Походка может многое сказать о здоровье человека. Об этом знает Тимоти Нийлер из Университета штата Пенсильвания — он собирает самую большую в мире коллекцию человеческих походок при помощи технологии захвата движения. В свою лабораторию он приглашает походить под 12 высокоскоростными камерами людей в возрасте от 18 до 65 лет. Нийлер прикрепляет на тело участников исследования около 40 светоотражающих датчиков. Таким образом формируется база данных о походке, которую впоследствии используют врачи. Прежде всего, знания о нормальной походке дают основу для выявления практически любой ортопедической проблемы, которая встречается у взрослых людей. Например, если необходимо измерить эффективность замены тазобедренного или коленного сустава, врачи «записывают» походку пациента и сравнивают с усреднёнными параметрами походки из базы данных.


Как размещают на теле пациента датчики для анализа походки? Датчик правого бедра должен находится на 1/3 расстояния между правым датчиком передней верхней подвздошной кости и датчиком правого колена. Датчик правой голени должен находится на 1/3 расстояния между датчиком правого колена и датчиком правой лодыжки. На левой части тела расстановка другая: датчики левого бедра находится на 2/3 расстояния между левым датчиком передней верхней подвздошной кости и датчиком левого колена. Датчик левой голени ставится на 2/3 расстояния между датчиками левого колена и левой лодыжки. Источник: Motion Analysis

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

Быстрее, выше, виднее: как захват движения помог баскетболистам?

Захват движения широко используется в спорте. Анализ действий игроков позволяет выявить их ошибки или испытать самое разное снаряжение. Кит Пэммент — главный тренер баскетбольной команды, которая состоит из людей, передвигающихся на креслах-колясках. По совместительству он инженер и давно интересовался, как захват движения может улучшить результативность баскетбольных команд. В частности, система захвата движения Perception Neuron помогла подобрать наиболее эффективные кресла-коляски для баскетболистов с ограниченными возможностями. Облачённые в костюмы спортсмены выполняли спринт, маневрирование, дриблинг и игру один на один и при этом поочередно испытывали шесть разных типов кресел, пока тренер изучал в реальном времени движение их анимационных моделей с помощью 16 камер.

Анимационные «аватары» спортсменов были интегрированы с 3D-моделями кресел-каталок. Источник: Rockets Science CIC / YouTube

За один день изучения тренировки игроков Кит выработал технические критерии к спортивным креслам для своей команды в зависимости от навыков спортсмена, его сильных и слабых сторон, техники движения и игры. Сейчас тренер готовит целый курс для своих коллег, работающих с такими же спортсменами.

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

Что дальше: роботы-аватары, телепортация и виртуальная реальность

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

В 2018 году группа ученых из Великобритании и Италии сумели подчинить робота под названием Centauro действиям хрупкой девушки посредством захвата движения. Для этого они использовали недорогую систему распознавания движений ASUS Xtion PRO, содержащую инфракрасные датчики и функцию распознавания цветных изображений (RGB). Она отвечала за захват движения. Обрабатывал полученную информацию OpenPose — основанный на машинном обучении алгоритм, способный обнаруживать в режиме реального времени (8-10 Гц) движения тела человека с использованием 2D-изображений RGB-камеры. Трехмерные координаты суставов и конечностей человека, полученные из системы, после фильтрации передаются для управления роботом Centauro.

Отказ от экзоскелетов для захвата движения в будущем позволит с помощью 3D-моделирования проводить весьма сложные работы, требующие хорошие навыки тонкой моторики рук. Источник: Dimitrios Kanoulas / YouTube

Конечно, пока Centauro нельзя вооружить автоматом и передать в управление опытному морпеху, но в будущем создание таких систем возможно. Еще в 2012 году Управление перспективных исследовательских проектов Министерства обороны США (Defense Advanced Research Projects Agency, DARPA) анонсировало проект под неожиданным названием «Аватар», в рамках которого предполагалась «разработка интерфейсов и алгоритмов, позволяющих солдату эффективно сотрудничать с полуавтономной машиной на двух ногах (bi-pedal) в качестве “суррогата” солдата». Очевидно, что такая система позволит фактически наделить робота навыками опытного и хорошо подготовленного солдата, и при этом сохранить ему жизнь в самой сложной оперативной обстановке благодаря удаленному управлению. О ходе исследований затем не сообщалось.

В 2018 году фонд XPrize объявил конкурс на создание робота-аватара ANA Avatar XPrize, а затем были отобраны 77 команд, которые в конце 2020 года представят свои разработки роботов-аватаров. По условиям конкурса разработчики должны соединить решения в области захвата движения, сенситивным технологиям, AR и VR. Конечная цель, за достижение которой дадут 5 млн долл. США, — создание системы, в которой оператор будет видеть, слышать, двигаться и выполнять различные задачи в удаленной среде.

У аватара при этом должно быть три режима работы:

  1. полностью управляемый режим: робот выполняет только команды человека;
  2. усовершенствованный: самостоятельное решение некоторых задач, к примеру, анализ окружающей среды или составление карты;
  3. полуавтономный: самостоятельное принятие решений.

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

ссылка на оригинал статьи https://habr.com/ru/company/toshibarus/blog/516560/

20 психологических уловок в дизайне продуктов

Совершенствование продуктов с применением когнитивных искажений и моделей убеждения.


Несколько лет назад коллега из моей бывшей компании (BlaBlaCar) познакомил меня с игрой Mental Notes. Разрабатывая какую-либо функцию, мы вместе с несколькими менеджерами по продукту, дизайнерами и разработчиками делали «поведенческий анализ», во время которого старались понять, как и какие принципы поведенческой психологии можно применить в проекте.

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

Для справки: представленные далее темы и их описания взяты из колоды карт Mental Notes.
 
 

Социальное доказательство

«В новых и незнакомых обстоятельствах мы склонны следовать образцам поведения тех, кто на нас похож».

Если в приложении нужно, чтобы пользователь выполнил необычное действие или завел новую некомфортную привычку, часто используют социальные доказательства: например, комментарии, оценки, отзывы и т. д.

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


Приложение для медитации Petit BamBou

Бесчисленные примеры социальных доказательств можно найти по мере приближения ко дну воронки покупок: например, соответствующие элементы часто расположены на странице с ценами:


Страницы с ценами в мобильных приложениях The Athletic и MasterClass

У более дорогого варианта часто бывает метка «Самый популярный»; иногда приводятся отзывы клиентов, призванные убедить купить наконец-то продукт или услугу.
 
 

Любопытство

«Если человека подразнить интересной информацией, он захочет узнать больше!»

Именно это делает фитнес-приложение Strava в своей функции «Анализ темпа»: вам дают предпросмотр того, как выглядит график, — но не более того. Чтобы увидеть подробный анализ, нужно купить подписку Summit.


Анализ темпа в Strava — триггер, побуждающий подписаться на премиум-версию

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


Веб-сайт Wall Street Journal

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

Узнать проще, чем вспомнить

«Пережитое ранее легче узнать, чем вспомнить».

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

Например, Airbnb дает восемь различных критериев оценки места, где вы останавливались. Это позволяет получить от гостя достаточно количественной информации в случае, если он не даст словесный отзыв. Кроме того, благодаря такому подходу гостям проще вспомнить определенные моменты своего опыта. Если бы Airbnb не предлагал все эти категории, то оценки наверняка были бы менее аккуратными: гости оценивали бы только самые приятные и неприятные впечатления, а про «середину» забывали бы.


Система оценок Airbnb

Поэтому и страховой стартап Lemonade предлагает несколько предопределенных категорий дорогостоящих предметов.


Последний этап процесса запроса расценок в Lemonade

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

Приятные моменты

«Мы вспоминаем и положительно реагируем на небольшие, неожиданные и веселые приятные моменты».

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


Скриншоты приложения Zenly

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


Если включить композицию из плейлистов «Звездных войн» на Spotify, то панель воспроизведения превратится в световой меч.
 
 

Привязка и приспосабливание

«Принимая решения, мы чересчур полагаемся — или привязываемся — к одному аспекту или фрагменту информации».

Например, при покупке ноутбука на сайте Apple.com вам предлагают множество вариантов (200 $ — мощнее процессор, 100 $ — лучше видеокарта, 400 $ — больше объем диска, 299,99 $ — предустановленный Final Cut Pro X, 199,99 $ — Logic Pro X). Если брать по отдельности, то за всё получится внушительная сумма. Но вы уже сделали мысленный скачок и платите за ноутбук 2799 $, поэтому кажется, что каждая опция — не так уж и дорого, верно?

Тот же подход применим, когда вы платите 150 $ за продукты на кассе, и вам предлагают пожертвовать на благотворительность 0,50 $ — не такая уж и большая сумма относительно всего чека, верно?
 
 

Умеренные трудности

«Нам нравится преодолевать трудности — особенно если в них найден баланс между чрезмерной сложностью и вызывающей зевоту легкостью».

По этой причине приложение для изучения языков Duolingo начинается с тестового задания — чтобы первые уроки соответствовали вашему уровню и вы не заскучали.


 
 

Эстетика как удобство использования

«Эстетичный дизайн часто воспринимается как более удобный в использовании».

Привлекательные элементы воспринимаются нами как более удобные в использовании. Приложение-календарь Sunrise, купленное Microsoft в 2015 г. за более чем 100 млн долл., на тот момент было лучшим в своей категории на платформах iOS и Android: тысячи пользователей хвалили его за понятный и удобный интерфейс.

Но действительно ли возможности Sunrise такие уникальные? В приложении было несколько удобных функций — например, интеграция с событиями в Facebook. Но ничего сверхнеобычного там не было — в том числе ничего, что календари от Apple или Google не могли бы скопировать за пару недель.

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

Установление ценности

«Мы ценим то, что стоит дороже».

Стоимость может выражаться и в деньгах, и во временны́х затратах.

В отношении денег можно привести множество не самых интуитивных примеров, когда компании повышали конверсию и (или) доход, просто подняв цены. В книге «Не сходите с ума на работе» Джейсон Фрайд и Дэвид Хайнемайер Хенссон рассказывают, что повышение начальной цены Basecamp с 29 $ до 99 $ в месяц позволило привлечь новых клиентов: иногда чем выше стоимость, тем более полезным кажется продукт.


Страница с ценами Basecamp

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

Боязнь потери

«Мы не любим терять или упускать то, что уже имеем (даже если есть возможность получить больше)».

Хороший пример этого психологического трюка часто можно увидеть при отмене подписки на некоторые сервисы. Например, если вы решите отменить тарифный план Adobe Creative Cloud, вы увидите следующее:


Процесс отмены подписки на Adobe Creative Cloud

Вам перечисляют всё, что вы потеряете — и в смысле финансов (из-за платы за досрочную отмену), и в смысле функций (которыми вы больше не сможете пользоваться).

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

Так же сделано и в Duolingo: если не зарегистрироваться, вы потеряете всю введенную исходную информацию, а также результаты первого пройденного «вводного» теста.

Боязнь потерь также широко эксплуатируется в условно-бесплатных приложениях с подпиской. Предположим, вы запустили вводный курс медитации в Headspace. Если вы прошли 10 занятий, можно сказать, что вы определенно намерены научиться медитации. Но если не оформить платную подписку, вы не сможете продолжить и потеряете накопленный «момент».
 
 

Контраст

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

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

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

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

А вот в Opodo иерархия действий не очень хорошо отражена контрастом: разделы выбора места, отправки по электронной почте, добавления багажа и входа в учетную запись выделены так же, как переход к следующим этапам. Даже если такая иерархия направлена на увеличение дополнительных продаж (скорее всего, так и есть), она с большой вероятностью приведет к снижению общей удовлетворенности пользователей.
 
 

Регулярные события

«Повторяющиеся события вызывают постоянный интерес, формируют ожидание и дают чувство принадлежности».

По этой причине в Headspace внедрили «групповую медитацию»: каждый день в определенное время все премиум-пользователи Headspace могут объединиться и вместе медитировать. Утверждается, что такая групповая медитация «поощряет формирование и соблюдение режима медитации». Но еще это — отличный способ обеспечить долгосрочное удержание пользователей и дать им чувство общности.


 
 

Последовательность действий

«Мы с большей вероятностью выполняем сложные действия, если она разбиты на более мелкие задачи».

Хороших примеров разбиения сложного действия на последовательность простых — бесчисленное множество. Продуктам часто удается разбить сложные процессы на удобные и быстрые действия. Например — процесс публикации в Airbnb или процесс перевода денег в TransferWise.

В этой статье я рассмотрю процесс регистрации в онлайн-банке N26: выделив каждому этапу регистрации отдельный экран, разработчики создают впечатление, что открыть банковский счет — проще простого!


Процесс регистрации в N26 в 2018 г.

На веб-сайте они даже дают такую рекламу: «Банковский счет в N26 — за 8 минут».

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

Ограниченный выбор

«Мы с большей вероятностью сделаем выбор, когда вариантов меньше».

В последние годы некоторые компании смогли сформировать свое ценностное предложение на упрощении количества предлагаемых вариантов.

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


Результаты поиска в приложении Virtuo

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

То же самое и с французской компанией по медицинскому страхованию — Alan, подход которой к ценообразованию противоположен традиционной в этой отрасли модели, где всё запутанно и сложно. Alan дает единственное предложение, которое рассчитывается на базе возраста клиента. Философия «простота прежде всего» — один из аспектов, за которые клиенты любят Alan.
 
 

Репутация

«Мы больше заботимся о своем поведении, когда это может повлиять на то, как нас воспринимают сверстники и общество».

В качестве примеров продуктов, опирающихся на понятие репутации, можно назвать Reddit и StackOverflow: за публикации и комментарии пользователи либо зарабатывают репутацию (получая положительные голоса, значки, трофеи, медали), либо теряют ее (получая отрицательные голоса).

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

Авторитет

«Мы предпочитаем следовать примеру и советам признанного авторитета».

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

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

AirHelp понимает эту проблему — поэтому они добавляют в процесс запроса элементы, обосновывающие его авторитетность:

  • Они упоминают «регламент EC261».
  • Они дают возможность пообщаться с человеком: «Джейн — ваш помощник в AirHelp».


Первая страница процесса оформления заявки на компенсацию на сайте AirHelp

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

Ограниченный доступ

«Мы естественным образом хотим то, что считаем эксклюзивным или принадлежащим избранному обществу».

Думаю, не нужно напоминать о шумихе, которая возникла вокруг Inbox — альтернативного приложения электронной почты от Google: когда оно вышло в ограниченный доступ пару лет назад, каждый пытался заполучить приглашение.

Приложение Inbox в итоге кануло в лету: компания Google закрыла его в 2019 г. Сервис предлагал пару новых (в сравнении с Gmail) функций, но ожидаемой революции в электронной почте он не произвел. Тем не менее, продвижение этого продукта на рынке было успешным. Подобный ажиотаж сейчас наблюдаются вокруг Superhuman и Clubhouse.

Еще один пример ограничения доступа — сообщество дизайнеров Dribbble.


Главная страница Dribbble

Просматривать страницы Dribbble может кто угодно, но чтобы опубликовать свою работу, нужно получить приглашение от участника платформы — это помогает не только поддерживать определенный уровень качества, но и придает сервису Dribbble эксклюзивности по сравнению с другими сообществами.
 
 

Самовыражение

«Мы ищем возможность выразить свою личность, свои чувства и мысли».

Это очень мощный принцип, который работает во всех возрастных группах.

Приложения, ориентированные на подростков, почти всегда дают возможность настроить профиль: изменить цвета, аватар, добавить классные аксессуары и т. д. Примеры — Pokémon Go, Snap (с Bitmoji) и (совсем недавно) Stadium Live.


Настройка аватара в Stadium Live

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


Настраиваемые рамки для фото в профиле Facebook

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

Использование принципа самовыражения укрепляет связь между продуктом и пользователями.
 
 

Дефицит

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

Безупречное мастерство в применении этого принципа демонстрирует Booking.com: при просмотре гостиниц на странице поиска нередко можно увидеть несколько визуальных подсказок, указывающих на дефицитное предложение.

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

  • «Забронировано 27 раз за последние 6 часов» (эта фраза также применяет принцип социального доказательства).
  • «Большой спрос: на сайте осталось всего 7 номеров!»

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

Юмористический эффект

«Поданная с юмором информация запоминается легче и радует пользователя».

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

Например, при поиске маршрута из пункта А в пункт Б иногда можно использовать функции «Телепорт», «Катапульта» или «Реактивный ранец» — и тогда Борис Джонсон переместится из А в Б менее чем за 5 секунд.


Предложение «катапультировать Бориса Джонсона» и правда звучит забавно.

Citymapper и платную подписку строит частично на таком юмористическом подходе.


Платная подписка Citymapper — Citymapper CLUB

Один из аргументов в пользу платной подписки Citymapper CLUB — поддержка разработчиков в том, что они делают, в том числе, чтобы они могли «сохранять свои причуды — ведь это тоже важно».
 
 

Непредсказуемые вознаграждения

«Случайные вознаграждения — сильные мотиваторы: они кажутся редкими и отличаются непредсказуемостью (а также реже противоречат внутренней мотивации)».

Один из элементов, благодаря которым Snapchat завоевал пользователей, — это «трофеи».


Трофеи в Snapchat

В разделе трофеев представлены награды, собранные вами в зависимости от ваших действий. Но вам не говорят ни как получить более крупный трофей за определенное действие, ни какие еще трофеи можно выиграть — это и делает их «непредсказуемыми вознаграждениями». Snapchat награждает трофеями за необычное использование приложения — например за отправку снимка между 4 и 5 часами утра или с фильтром, показывающим температуру ниже нуля.

Другие приложения — например, Tinder, — в своей стратегии по дифференциации тоже сделали ставку на непредсказуемые вознаграждения. Листая Tinder, вы никогда не знаете, что увидите дальше: заложив таким образом непредсказуемость в саму основу приложения, разработчики сделали его очень затягивающим.

Подробнее о непредсказуемых вознаграждениях и геймификации в этой колоде в целом пишет Mozza: Mobile Gamification — How The Best Apps Nailed It (Waze, Duolingo, Tinder, Snapchat, LinkedIn, Zenly…).


Надеюсь, вам понравились эти примеры! Может, и вы знаете примеры реализации психологических принципов в дизайне продукта? Расскажите о них!

Если вас заинтересовали Mental Notes и соответствующая карточная колода, то, к сожалению, заказать ее уже нельзя. Но похожую информационную подборку можно найти здесь: плакат Persuasive Patterns Poster от UI Patterns.

Если вам понравилась эта статья и вас интересует применение психологии в дизайне продуктов, я рекомендую зайти на отличную страницу Growth.design «The Psychology of Design», где составлен еще более длинный список (101 пункт!) когнитивных искажений и принципов, влияющих на удобство пользования.
 
 
О переводчике

Перевод статьи выполнен в Alconost.

Alconost занимается локализацией игр, приложений и сайтов на 70 языков. Переводчики-носители языка, лингвистическое тестирование, облачная платформа с API, непрерывная локализация, менеджеры проектов 24/7, любые форматы строковых ресурсов.

Мы также делаем рекламные и обучающие видеоролики — для сайтов, продающие, имиджевые, рекламные, обучающие, тизеры, эксплейнеры, трейлеры для Google Play и App Store.

→ Подробнее

ссылка на оригинал статьи https://habr.com/ru/company/alconost/blog/516854/