Як змінити статус користувача FORCE_CHANGE_PASSWORD?


97

Використовуючи AWS Cognito, я хочу створити фіктивних користувачів для тестування.

Потім я використовую AWS Console для створення такого користувача, але користувач має свій статус набір для FORCE_CHANGE_PASSWORD. З цим значенням цього користувача не можна автентифікувати.

Чи є спосіб змінити цей статус?

ОНОВЛЕННЯ Така ж поведінка під час створення користувача з CLI


1
Відповідь користувача надав @joe
donlys

Відповіді:


15

Вибачте, у вас виникли труднощі. У нас немає однокрокового процесу, коли ви можете просто створювати користувачів та автентифікувати їх безпосередньо. Ми можемо змінити це в майбутньому, наприклад, щоб дозволити адміністраторам встановлювати паролі, якими користувачі можуть безпосередньо користуватися. Наразі, коли ви створюєте користувачів за допомогою програми AdminCreateUserабо за допомогою реєстрації користувачів у додатку, потрібні додаткові кроки, які примушують користувачів змінювати пароль при вході в систему, або користувачі перевіряють адресу електронної пошти або номер телефону, щоб змінити статус користувача на CONFIRMED.


4
На даний момент, коли ви створюєте користувачів або за допомогою AdminCreateUser, або підписуючи користувачів за допомогою програми, потрібні додаткові кроки, або примушування користувачів змінювати пароль при вході, або надання користувачам підтвердження електронної пошти або номера телефону, щоб змінити статус користувача на ПІДТВЕРДЖЕНО. Що саме являють собою ці додаткові електронні елементи та як я можу їх викликати з JS SDK.
Саураб Тіварі

3
@joe зазначив, що тепер це можливо, оскільки це було додано. шукати --permanentпрапор: stackoverflow.com/a/56948249/3165552
Isaias-б

150

Я знаю, що минув якийсь час, але думав, що це може допомогти іншим людям, які стикаються з цим повідомленням.

Ви можете використовувати CLI AWS для зміни пароля користувачів, проте це багатоетапний процес:


Крок 1: Отримайте маркер сеансу для потрібного користувача:

aws cognito-idp admin-initiate-auth --user-pool-id %USER POOL ID% --client-id %APP CLIENT ID% --auth-flow ADMIN_NO_SRP_AUTH --auth-parameters USERNAME=%USERS USERNAME%,PASSWORD=%USERS CURRENT PASSWORD%

Якщо це повертає помилку про Unable to verify secret hash for client, створіть інший клієнт програми без секрету та використовуйте цей ідентифікатор клієнта.

Крок 2: Якщо крок 1 буде успішним, він відповість викликом NEW_PASSWORD_REQUIRED, іншими параметрами виклику та ключем сеансу користувачів. Потім ви можете виконати другу команду, щоб видати відповідь на виклик:

aws cognito-idp admin-respond-to-auth-challenge --user-pool-id %USER POOL ID% --client-id %CLIENT ID% --challenge-name NEW_PASSWORD_REQUIRED --challenge-responses NEW_PASSWORD=%DESIRED PASSWORD%,USERNAME=%USERS USERNAME% --session %SESSION KEY FROM PREVIOUS COMMAND with ""%

Якщо з’являється повідомлення про Invalid attributes given, XXX is missingпередачу відсутніх атрибутів за допомогою форматуuserAttributes.$FIELD_NAME=$VALUE

Вищенаведена команда повинна повернути дійсний результат аутентифікації та відповідні маркери.


Важливо: щоб це працювало, у пулі користувачів Cognito ПОВИНЕН бути налаштований функціонал клієнта програмиADMIN_NO_SRP_AUTH ( крок 5 у цьому документі ).


25
На диво корисно. Ще дві поради: якщо ви отримуєте повідомлення про помилку "Неможливо перевірити секретний хеш для клієнта", створіть ще один клієнт програми без секрету та скористайтеся цим ( stackoverflow.com/questions/37438879/… ). Якщо з’являється повідомлення про помилку " Надано недійсні атрибути, XXX відсутній", передайте відсутні атрибути, використовуючи формат userAttributes.$FIELD_NAME=$VALUE( github.com/aws/aws-sdk-js/issues/1290 ).
Лейн Реттіг,

Якщо ви не можете вивести користувача з FORCE_CHANGE_PASSWORD, за допомогою будь-якої з команд CLI (включаючи цю відповідь) спробуйте "admin-disable-user", а потім "admin-enable-user" або скористайтеся консоллю. Тоді або користуйтеся цим процесом, або ви можете використовувати звичайний потік скидання пароля. Іноді користувач "закінчується", якщо не ввійшов до когніто в межах визначеного ліміту. (за замовчуванням 7 днів, я думаю)
comfytoday

Спробував з CLI і всередині лямбда, отримав таку помилку: Дано
невірні

1
@misher ви отримуєте це завдяки необхідним атрибутам. ви можете включити їх у дзвінок, але синтаксис трохи дивний:--challenge-responses NEW_PASSWORD=password,USERNAME=username,userAttributes.picture=picture,userAttributes.name=name
edzillion

90

Це нарешті додано до AWSCLI: https://docs.aws.amazon.com/cli/latest/reference/cognito-idp/admin-set-user-password.html

Ви можете змінити пароль користувача та оновити статус за допомогою:

aws cognito-idp admin-set-user-password --user-pool-id <your user pool id> --username user1 --password password --permanent

Перш ніж використовувати це, можливо, вам доведеться оновити свій CLI AWS, використовуючи:

pip3 install awscli --upgrade


13
Це останнє та найефективніше рішення!
donlys

7
Це має бути відповіддю.
Містер Янг

4
Найкраще та найпростіше рішення у 2020 році. Дякую!
тюдор

23

Просто додайте цей код після вашої onSuccess: function (result) { ... },функції входу. Тоді ваш користувач матиме статус ПІДТВЕРДЖЕНО .

newPasswordRequired: function(userAttributes, requiredAttributes) {
    // User was signed up by an admin and must provide new
    // password and required attributes, if any, to complete
    // authentication.

    // the api doesn't accept this field back
    delete userAttributes.email_verified;

    // unsure about this field, but I don't send this back
    delete userAttributes.phone_number_verified;

    // Get these details and call
    cognitoUser.completeNewPasswordChallenge(newPassword, userAttributes, this);
}

1
Це працює для мене. Ви навіть можете ввести поточний пароль, якщо не хочете його змінювати.
mvandillen

Рекурсія FTW! Дякую! (рекурсія - це thisповний новий виклик пароля)
Paul S

22

Ви можете змінити цей статус користувача FORCE_CHANGE_PASSWORD, зателефонувавши respondToAuthChallenge()користувачеві так:

var params = {
  ChallengeName: 'NEW_PASSWORD_REQUIRED', 
  ClientId: 'your_own3j6...0obh',
  ChallengeResponses: {
    USERNAME: 'user3',
    NEW_PASSWORD: 'changed12345'
  },
  Session: 'xxxxxxxxxxZDMcRu-5u...sCvrmZb6tHY'
};

cognitoidentityserviceprovider.respondToAuthChallenge(params, function(err, data) {
  if (err) console.log(err, err.stack); // an error occurred
  else     console.log(data);           // successful response
});

Після цього ви побачите в консолі, що user3статус CONFIRMED.


1
Я не розумію, як ти сюди потрапив. Що ви зателефонували, щоб отримати сесію, яку ви провели XXX? Коли я телефоную до adminInitiateAuth, з’являється повідомлення про помилку UserNotFoundException.
Ryan Shillington,

3
Вибачте, якщо ця відповідь не була кристально зрозумілою. Ось докладніші відомості: 1. У пулі користувачів є клієнт під назвою 'your_own3j63rs8j16bxxxsto25db00obh', який створюється БЕЗ сформованого секретного ключа. Наведений вище код не буде працювати, якщо клієнту присвоєно ключ. 2) Ключ сеансу - це значення, яке повертається за викликомcognitoidentityserviceprovider.adminInitiateAuth({ AuthFlow: 'ADMIN_NO_SRP_AUTH', ClientId: 'your_own3j63rs8j16bxxxsto25db00obh', UserPoolId: 'us-east-1_DtNSUVT7n', AuthParameters: { USERNAME: 'user3', PASSWORD: 'original_password' } }, callback);
Аріель Араза

3) user3був створений у консолі та спочатку отримав пароль'original_password'
Аріель Араза,

В ПОРЯДКУ. Зараз я зрозумів, чому я отримував UserNotFoundException. Це було тому, що я використовував псевдонім як ім’я користувача для входу, який чудово працює в JS API, але, мабуть, не працює з adminInitiateAuth. Дякую Аріель Араза, я ціную вашу допомогу.
Райан Шилінгтон,

Ага! Я нарешті змусив це працювати. Дякую! Дякую Аріель!
Райан Шилінгтон,

11

не впевнений, що ви все ще боретеся з цим, але для створення групи тестових користувачів я використовував awscliяк такий:

  1. Скористайтеся підкомандою реєстрації відgnito-idp, щоб створити користувача
aws cognito-idp sign-up \
   --region %aws_project_region% \
   --client-id %aws_user_pools_web_client_id% \
   --username %email_address% \
   --password %password% \
   --user-attributes Name=email,Value=%email_address%
  1. Підтвердьте користувача за допомогою адміністратора-підтвердження-реєстрації
aws cognito-idp admin-confirm-sign-up \
--user-pool-id %aws_user_pools_web_client_id% \
--username %email_address%

5

ОНОВЛЕННЯ:

Зараз я використовую це, перекладене для посилення, усередині лямбди NodeJS:

// enable node-fetch polyfill for Node.js
global.fetch = require("node-fetch").default;
global.navigator = {};

const AWS = require("aws-sdk");
const cisp = new AWS.CognitoIdentityServiceProvider();

const Amplify = require("@aws-amplify/core").default;
const Auth = require("@aws-amplify/auth").default;

...


/*
  this_user: {
    given_name: string,
    password: string,
    email: string,
    cell: string
  }
*/
const create_cognito = (this_user) => {
  let this_defaults = {
    password_temp: Math.random().toString(36).slice(-8),
    password: this_user.password,
    region: global._env === "prod" ? production_region : development_region,
    UserPoolId:
      global._env === "prod"
        ? production_user_pool
        : development_user_pool,
    ClientId:
      global._env === "prod"
        ? production_client_id
        : development_client_id,
    given_name: this_user.given_name,
    email: this_user.email,
    cell: this_user.cell,
  };

  // configure Amplify
  Amplify.configure({
    Auth: {
      region: this_defaults.region,
      userPoolId: this_defaults.UserPoolId,
      userPoolWebClientId: this_defaults.ClientId,
    },
  });
  if (!Auth.configure())
    return Promise.reject("could not configure amplify");

  return new Promise((resolve, reject) => {
    let _result = {};

    let this_account = undefined;
    let this_account_details = undefined;

    // create cognito account
    cisp
      .adminCreateUser({
        UserPoolId: this_defaults.UserPoolId,
        Username: this_defaults.given_name,
        DesiredDeliveryMediums: ["EMAIL"],
        ForceAliasCreation: false,
        MessageAction: "SUPPRESS",
        TemporaryPassword: this_defaults.password_temp,
        UserAttributes: [
          { Name: "given_name", Value: this_defaults.given_name },
          { Name: "email", Value: this_defaults.email },
          { Name: "phone_number", Value: this_defaults.cell },
          { Name: "email_verified", Value: "true" },
        ],
      })
      .promise()
      .then((user) => {
        console.warn(".. create_cognito: create..");
        _result.username = user.User.Username;
        _result.temporaryPassword = this_defaults.password_temp;
        _result.password = this_defaults.password;

        // sign into cognito account
        return Auth.signIn(_result.username, _result.temporaryPassword);
      })
      .then((user) => {
        console.warn(".. create_cognito: signin..");

        // complete challenge
        return Auth.completeNewPassword(user, _result.password, {
          email: this_defaults.email,
          phone_number: this_defaults.cell,
        });
      })
      .then((user) => {
        console.warn(".. create_cognito: confirmed..");
        this_account = user;
        // get details
        return Auth.currentAuthenticatedUser();
      })
      .then((this_details) => {
        if (!(this_details && this_details.attributes))
          throw "account creation failes";

        this_account_details = Object.assign({}, this_details.attributes);

        // signout
        return this_account.signOut();
      })
      .then(() => {
        console.warn(".. create_cognito: complete");
        resolve(this_account_details);
      })
      .catch((err) => {
        console.error(".. create_cognito: error");
        console.error(err);
        reject(err);
      });
  });
};

Я встановлюю тимчасовий пароль, а потім згодом скидаю його до запитуваного користувачем пароля.

СТАРИЙ ПОСТ:

Ви можете вирішити це за допомогою пакета SDK amazon -gnitogito-ident-js шляхом автентифікації за допомогою тимчасового пароля після створення облікового запису cognitoidentityserviceprovider.adminCreateUser()та запуску cognitoUser.completeNewPasswordChallenge()всередині cognitoUser.authenticateUser( ,{newPasswordRequired})- все це всередині функції, яка створює вашого користувача.

Я використовую наведений нижче код всередині лямбда-коду AWS для створення ввімкнених облікових записів користувачів Cognito. Я впевнений, що це можна оптимізувати, будьте терплячі зі мною. Це мій перший допис, і я все ще досить новий у JavaScript.

var AWS = require("aws-sdk");
var AWSCognito = require("amazon-cognito-identity-js");

var params = {
    UserPoolId: your_poolId,
    Username: your_username,
    DesiredDeliveryMediums: ["EMAIL"],
    ForceAliasCreation: false,
    MessageAction: "SUPPRESS",
    TemporaryPassword: your_temporaryPassword,
    UserAttributes: [
        { Name: "given_name", Value: your_given_name },
        { Name: "email", Value: your_email },
        { Name: "phone_number", Value: your_phone_number },
        { Name: "email_verified", Value: "true" }
    ]
};

var cognitoidentityserviceprovider = new AWS.CognitoIdentityServiceProvider();
let promise = new Promise((resolve, reject) => {
    cognitoidentityserviceprovider.adminCreateUser(params, function(err, data) {
        if (err) {
            reject(err);
        } else {
            resolve(data);
        }
    });
});

promise
    .then(data => {
        // login as new user and completeNewPasswordChallenge
        var anotherPromise = new Promise((resolve, reject) => {
            var authenticationDetails = new AWSCognito.AuthenticationDetails({
                Username: your_username,
                Password: your_temporaryPassword
            });
            var poolData = {
                UserPoolId: your_poolId,
                ClientId: your_clientId
            };
            var userPool = new AWSCognito.CognitoUserPool(poolData);
            var userData = {
                Username: your_username,
                Pool: userPool
            };

            var cognitoUser = new AWSCognito.CognitoUser(userData);
            let finalPromise = new Promise((resolve, reject) => {
                cognitoUser.authenticateUser(authenticationDetails, {
                    onSuccess: function(authResult) {
                        cognitoUser.getSession(function(err) {
                            if (err) {
                            } else {
                                cognitoUser.getUserAttributes(function(
                                    err,
                                    attResult
                                ) {
                                    if (err) {
                                    } else {
                                        resolve(authResult);
                                    }
                                });
                            }
                        });
                    },
                    onFailure: function(err) {
                        reject(err);
                    },
                    newPasswordRequired(userAttributes, []) {
                        delete userAttributes.email_verified;
                        cognitoUser.completeNewPasswordChallenge(
                            your_newPoassword,
                            userAttributes,
                            this
                        );
                    }
                });
            });

            finalPromise
                .then(finalResult => {
                    // signout
                    cognitoUser.signOut();
                    // further action, e.g. email to new user
                    resolve(finalResult);
                })
                .catch(err => {
                    reject(err);
                });
        });
        return anotherPromise;
    })
    .then(() => {
        resolve(finalResult);
    })
    .catch(err => {
        reject({ statusCode: 406, error: err });
    });

@Tom - це працює для вас? що-небудь я можу пояснити?
qqan.ny

Зараз набагато краще.
Том Аранда,

@ qqan.ny одночасно використовувати обіцянки та зворотні дзвінки? Чому?
Юрій Гольський

@Iurii Golskyi - тоді я не знав кращого, щойно почав вивчати як AWS, так і JS з нуля.
qqan.ny

4

Для Java SDK, припускаючи, що ваш клієнт Cognito налаштований, і у вас є користувач у стані FORCE_CHANGE_PASSWORD, ви можете зробити наступне, щоб отримати ПІДТВЕРДЖЕНОГО користувача ... а потім підтвердити його як зазвичай.

AdminCreateUserResult createUserResult = COGNITO_CLIENT.adminCreateUser(createUserRequest());

AdminInitiateAuthResult authResult = COGNITO_CLIENT.adminInitiateAuth(authUserRequest());


Map<String,String> challengeResponses = new HashMap<>();
challengeResponses.put("USERNAME", USERNAME);
challengeResponses.put("NEW_PASSWORD", PASSWORD);
RespondToAuthChallengeRequest respondToAuthChallengeRequest = new RespondToAuthChallengeRequest()
      .withChallengeName("NEW_PASSWORD_REQUIRED")
      .withClientId(CLIENT_ID)
      .withChallengeResponses(challengeResponses)
      .withSession(authResult.getSession());

COGNITO_CLIENT.respondToAuthChallenge(respondToAuthChallengeRequest);

Сподіваюся, це допоможе у цих тестах інтеграції (Вибачте за форматування)


4

В основному це та сама відповідь, але для .Net C # SDK:

Далі буде зроблено повне створення користувача адміністратора із бажаними іменем користувача та паролем. Маючи таку модель користувача:

public class User
{
    public string Username { get; set; }
    public string Password { get; set; }
}

Ви можете створити користувача та зробити його готовим до використання, використовуючи:

   public void AddUser(User user)
    {
        var tempPassword = "ANY";
        var request = new AdminCreateUserRequest()
        {
            Username = user.Username,
            UserPoolId = "MyuserPoolId",
            TemporaryPassword = tempPassword
        };
        var result = _cognitoClient.AdminCreateUserAsync(request).Result;
        var authResponse = _cognitoClient.AdminInitiateAuthAsync(new AdminInitiateAuthRequest()
        {
            UserPoolId = "MyuserPoolId",
            ClientId = "MyClientId",
            AuthFlow = AuthFlowType.ADMIN_NO_SRP_AUTH,
            AuthParameters = new Dictionary<string, string>()
            {
                {"USERNAME",user.Username },
                {"PASSWORD", tempPassword}
            }
        }).Result;
        _cognitoClient.RespondToAuthChallengeAsync(new RespondToAuthChallengeRequest()
        {
         ClientId = "MyClientId",
            ChallengeName = ChallengeNameType.NEW_PASSWORD_REQUIRED,
            ChallengeResponses = new Dictionary<string, string>()
            {
                {"USERNAME",user.Username },
                {"NEW_PASSWORD",user.Password }
            },
            Session = authResponse.Session
        });
    }

4

Якщо ви намагаєтеся змінити статус адміністратора з консолі. Потім виконайте наведені нижче дії після створення користувача.

  1. У Cognito перейдіть -> "керувати пулом користувачів" ->
  2. Перейдіть до "Налаштування клієнта програми" в розділі інтеграції додатків.
  3. Перевірте наведені нижче пункти i) Пул користувачів Cognito ii) Надання коду авторизації iii) Неявний дозвіл iv) Телефон v) Електронна пошта vi) openid vii) aws.cognito.signin.user.admin viii) профіль
  4. Введіть URL-адресу зворотного дзвінка своєї програми. Якщо ви не впевнені, введіть, наприклад: https://google.com, а пізніше ви можете змінити його на фактичну URL-адресу зворотного дзвінка
  5. натисніть на збереження змін.
  6. Після збереження змін натисніть на посилання "Запустити розміщений інтерфейс"
  7. Введіть облікові дані нового створеного користувача
  8. Скиньте пароль із новими обліковими даними та надішліть їх користувачеві

Крок 2

крок 3 4 5 6

крок 7

крок 8


2

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

func sessionWithDefaultRegion(region string) *session.Session {
    sess := Session.Copy()
    if v := aws.StringValue(sess.Config.Region); len(v) == 0 {
        sess.Config.Region = aws.String(region)
    }

    return sess
}



func (c *CognitoAppClient) ChangePassword(userName, currentPassword, newPassword string)   error {

    sess := sessionWithDefaultRegion(c.Region)
    svc := cognitoidentityprovider.New(sess)

    auth, err := svc.AdminInitiateAuth(&cognitoidentityprovider.AdminInitiateAuthInput{
        UserPoolId:aws.String(c.UserPoolID),
        ClientId:aws.String(c.ClientID),
        AuthFlow:aws.String("ADMIN_NO_SRP_AUTH"),
        AuthParameters: map[string]*string{
            "USERNAME": aws.String(userName),
            "PASSWORD": aws.String(currentPassword),
        },

    })



    if err != nil {
        return err
    }

    request := &cognitoidentityprovider.AdminRespondToAuthChallengeInput{
        ChallengeName: aws.String("NEW_PASSWORD_REQUIRED"),
        ClientId:aws.String(c.ClientID),
        UserPoolId: aws.String(c.UserPoolID),
        ChallengeResponses:map[string]*string{
            "USERNAME":aws.String(userName),
            "NEW_PASSWORD": aws.String(newPassword),
        },
        Session:auth.Session,
    }


    _, err = svc.AdminRespondToAuthChallenge(request)

    return err 
}

Ось одиничний тест:

import (
    "fmt"
    "github.com/aws/aws-sdk-go/service/cognitoidentityprovider"
    . "github.com/smartystreets/goconvey/convey"
    "testing"
)


func TestCognitoAppClient_ChangePassword(t *testing.T) {


    Convey("Testing ChangePassword!", t, func() {
        err := client.ChangePassword("user_name_here", "current_pass", "new_pass")



        Convey("Testing ChangePassword Results!", func() {
            So(err, ShouldBeNil)

        })

    })
}

1

В ПОРЯДКУ. Нарешті у мене є код, де адміністратор може створити нового користувача. Процес проходить так:

  1. Адміністратор створює користувача
  2. Користувач отримує електронний лист із тимчасовим паролем
  3. Користувач входить і просить змінити пароль

Крок 1 - найскладніша частина. Ось мій код для створення користувача в Node JS:

let params = {
  UserPoolId: "@cognito_pool_id@",
  Username: username,
  DesiredDeliveryMediums: ["EMAIL"],
  ForceAliasCreation: false,
  UserAttributes: [
    { Name: "given_name", Value: firstName },
    { Name: "family_name", Value: lastName},
    { Name: "name", Value: firstName + " " + lastName},
    { Name: "email", Value: email},
    { Name: "custom:title", Value: title},
    { Name: "custom:company", Value: company + ""}
  ],
};
let cognitoIdentityServiceProvider = new AWS.CognitoIdentityServiceProvider();
cognitoIdentityServiceProvider.adminCreateUser(params, function(error, data) {
  if (error) {
    console.log("Error adding user to cognito: " + error, error.stack);
    reject(error);
  } else {
    // Uncomment for interesting but verbose logging...
    //console.log("Received back from cognito: " + CommonUtils.stringify(data));
    cognitoIdentityServiceProvider.adminUpdateUserAttributes({
      UserAttributes: [{
        Name: "email_verified",
        Value: "true"
      }],
      UserPoolId: "@cognito_pool_id@",
      Username: username
    }, function(err) {
      if (err) {
        console.log(err, err.stack);
      } else {
        console.log("Success!");
        resolve(data);
      }
    });
  }
});

По суті, вам потрібно надіслати другу команду, щоб примусити електронний лист вважатися підтвердженим. Користувачеві все ще потрібно перейти на свою електронну пошту, щоб отримати тимчасовий пароль (який також підтверджує електронну адресу). Але без другого виклику, який встановлює електронну адресу підтвердженою, ви не отримаєте правильного дзвінка для скидання пароля.


Я все ще не отримую електронного листа, будь-яка пропозиція?
Вініцій

Дивно, чи правильно ви встановили електронну адресу в Cognito, з доступом до SES тощо? Cogntio не просто надсилатиме електронні листи людям, доки ви не підтвердите адресу електронної пошти, на яку ви намагаєтесь надіслати, або не отримаєте схвалення надсилати кому-небудь.
Райан Шилінгтон,

Я отримую код підтвердження електронною поштою, тому налаштування правильні. Просто електронне повідомлення з тимчасовим паролем ніколи не надходить ... Я в підсумку створив лямбду, прикріплену до тригера попередньої реєстрації, щоб відправити електронне повідомлення.
Вініцій
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.