Вход в базу данных Unity C # w. Игровые предпочтения не работают - PullRequest
0 голосов
/ 12 октября 2019

Я сделал базу данных в Unity с C #, и по какой-то причине она не работает. Я создал статический класс со всеми переменными static и сделал функции сохранения, загрузки, проверки, создания и сброса. Я также сделал скрипт, который находится на ScriptHolder GameObject и со всеми ссылками Buttons и InputFields. Вот мой код:

Класс базы данных:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public static class Database
{
    public enum Roles{
        Teacher,
        Student
    }
    public static Dictionary<string, string> logins = new Dictionary<string, string>();
    public static List<Roles> roles = new List<Roles>();

    public static void AddUser(string username, string password, Roles role)
    {
        logins.Add(username, password);
        roles.Add(role);
    }

    public static void SaveUsers()
    {
        LoadUsers();
        PlayerPrefs.DeleteAll();
        if(logins.Count != roles.Count)
        {
            throw new System.Exception("Roles count and login count do not match!");
        }
        int counter = 0;
        foreach (KeyValuePair<string, string> login in logins)
        {
            PlayerPrefs.SetString(login.Key, login.Value);
            PlayerPrefs.SetString("usernames", "");
            PlayerPrefs.SetString("usernames", PlayerPrefs.GetString("usernames") + "/" + login.Key);
            PlayerPrefs.SetInt(login.Key, (int)roles[counter]);
            counter += 1;
            Debug.Log(PlayerPrefs.GetString("usernames") + "/" + login.Key);
        }
    }

    public static void LoadUsers()
    {
        logins = new Dictionary<string, string>();
        roles = new List<Roles>();
        foreach (string key in PlayerPrefs.GetString("usernames").Split('/'))
        {
            logins.Add(key, PlayerPrefs.GetString(key));
            roles.Add((Roles)PlayerPrefs.GetInt(key));
        }

    }

    public static void FactorySettings()
    {
        PlayerPrefs.DeleteAll();
        LoadUsers();
        logins = new Dictionary<string, string>();
    }

    public static bool CheckUser(string username, string password)
    {
        if (logins.ContainsKey(username))
        {
            if (password == logins[username])
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        else
        {
            return false;
        }
    }
}

ScriptHolder:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using TMPro;

public class LoginProcess : MonoBehaviour
{
    public TextMeshProUGUI username;
    public TextMeshProUGUI password;

    public Slider loadingSlider;

    public GameObject error;
    public GameObject loading;

    public float progress = 0f;

    public Dictionary<string, string> show = Database.logins;

    public void Login()
    {
        if(Database.CheckUser(username.text, password.text))
        {
            StartCoroutine(LoadAsync(1));
            loading.SetActive(true);
        }
        else
        {
            error.SetActive(false);
        }
    }

    public void Create()
    {
        Database.AddUser(username.text, password.text, Database.Roles.Teacher);
        Database.SaveUsers();
        Database.LoadUsers();
    }

    public void _Reset()
    {
        Database.FactorySettings();
    }

    public void Start()
    {
        Database.LoadUsers();
    }

    IEnumerator LoadAsync (int index)
    {
        AsyncOperation operation = SceneManager.LoadSceneAsync(index);

        while (!operation.isDone)
        {
            float _progress = Mathf.Clamp01(operation.progress / .9f);

            progress = _progress;

            yield return null;
        }
    }
    public void Update()
    {
        if (progress != 0)
        {
            loadingSlider.value = progress;
        }

        if (Input.GetMouseButtonDown(0) && error.activeSelf)
        {
            error.SetActive(true);
        }
    }
}

Моя ошибка:

ArgumentException: элементс таким же ключом уже был добавлен. Key: System.Collections.Generic.Dictionary'2 [TKey, TValue] .TryInsert (ключ TKey, значение TValue, поведение System.Collections.Generic.InsertionBehavior) (в <599589bf4ce248909b8a14cbe4a2034e>: 0 .ictionary.Gol. 2 [TKey, TValue] .Add (клавиша TKey, значение TValue) (при <599589bf4ce248909b8a14cbe4a2034e>: 0)

1 Ответ

1 голос
/ 12 октября 2019

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


Что вы не понимаете в ошибке?

Очевидно, вы пытаетесь .Add пару ключ-значение к Dictionary, когда данный key уже существует в этом Dictionary.

В AddUser нет проверки, чтобы поймать такой случай, поэтому, возможно, вам лучше сделать что-то вроде

public static void AddUser(string username, string password, Roles role)
{
    // in general check if a string is empty!
    if(username.IsNullOrWhitespace())
    {
        // TODO: SHOW AN ERROR IN THE GUI THAT USERNAME MAY NOT BE EMPTY
        Debug.LogWarning("username may not be empty!", this);
        return;
    }

    if(logins.ContainsKey(username))
    {
        // TODO: SHOW AN ERROR IN THE GUI THAT THIS USER ALREADY EXISTS
        Debug.LogWarning($"A User with name \"{username}\" already exists! Please chose another username.", this);
        return;
    }

    logins.Add(username, password);
    roles.Add(role);
}

В общем, в вашем коде есть несколько действительно странных порядков, например

Database.AddUser (username.text, password.text, Database.Roles.Teacher);Database.SaveUsers ();Database.LoadUsers ();

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


Или если вы сделаете FactorySettings

PlayerPrefs.DeleteAll();
LoadUsers();
logins = new Dictionary<string, string>();

Сначала вы удалите все PlayerPRefs, а затем LoadUsers. зная, что в любом случае не должно быть никакого результата, кроме сброса logins и roles, а затем вы снова сбросите logins. Это довольно избыточно. Вы можете просто сказать

PlayerPrefs.DeleteAll();
logins.Clear();
roles.Clear();

Или еще раз взглянуть на метод SaveUsers: в вашем цикле foreach вы делаете

 PlayerPrefs.SetString(login.Key, login.Value);
 PlayerPrefs.SetString("usernames", "");
 PlayerPrefs.SetString("usernames", PlayerPrefs.GetString("usernames") + "/" + login.Key);

, так что, очевидно, вы все равно сохранитекаждая пара username, password индивидуально ... так зачем вообще использовать другую вещь?

Теперь перейдем к другой вещи: вы сбрасываете PlayerPrefs.SetString("usernames", ""); в каждой итерации цикла ... таквсегда будет храниться только один username/password!


Также мне кажется немного странным / неуверенным, что для сопоставления имени пользователя с паролем вы используете словарьно когда дело доходит до этой роли пользователя (что почти одинаково важно), вы просто используете список и получаете доступ к нему по индексу.


Итак, в целом

Я бы предпочел использовать подходящий класскак например

[Serializable]
public class User
{
    private const SECRET_SALT = "1234My_Example";

    public string Name;

    // Never store a naked Password!
    // Rather store the hash and everytime you want to check the password
    // compare the hashes!
    public string PasswordHash;

    // As said actually you should also not store this as a simple modifiable value
    // Since it almost has the same security impact as a password!
    public Roles Role;

    public User(string name, string password, Roles role)
    {
        Name = name;
        // Instead of the raw password store a hash
        PasswordHash = GetHash(password);
        Role = role;
    }

    // Compare the hash of the given password attempt to the stored one
    public bool CheckPassword(string attemptedPassword, string hash)
    {
        var base64AttemptedHash = GetHash(attemptedPassword);

        return base64AttemptedHash.Equals(PasswordHash);
    }

    private static string GetHash(string password)
    {
        // Use the secret salt so users can not simply edit the stored file
        // and add a users password brute-forcing the known blank hashing methods
        var unhashedBytes = Encoding.Unicode.GetBytes(SECRET_SALT + password);

        var sha256 = new SHA256Managed();
        var hashedBytes = sha256.ComputeHash(unhashedBytes);

        return Convert.ToBase64String(hashedBytes);
    }
}

И затем, например, это как DataBase.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Cryptography;
using System.Text;
using UnityEditor;
using UnityEngine;


public static class DataBase
{
    // Do not make the Dictionary public otherwise it can be modified by any class!
    // Rather only provide specific setters and getters
    private static Dictionary<string, User> _users = new Dictionary<string, User>();

    // You might even want to give this bit a less obvious name ;)
    private const string fileName = "DataBaseCredentials.cfg";

    private static string filePath;

    // This method will be automatically called on app start
    [InitializeOnLoadMethod]
    private static void Initialize()
    {
        filePath = Path.Combine(Application.persistentDataPath, fileName);

        if (!Directory.Exists(Application.persistentDataPath))
        {
            Directory.CreateDirectory(Application.persistentDataPath);
        }

        if (!File.Exists(filePath))
        {
            File.Create(filePath);
        }

        LoadUsers();
    }

    private static void LoadUsers()
    {
        Debug.Log("DataBase: Loading Users from " + filePath);

        using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
        {
            if (stream.Length == 0) return;

            var bf = new BinaryFormatter();
            _users = (Dictionary<string, User>)bf.Deserialize(stream);
        }
    }

    private static void SaveUsers()
    {
        Debug.Log("DataBase: Storing Users to " + filePath);

        using (var stream = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Write))
        {
            var bf = new BinaryFormatter();
            bf.Serialize(stream, _users);
        }

        LoadUsers();
    }

    public static User GetUserByName(string username)
    {
        if (string.IsNullOrWhiteSpace(username))
        {
            Debug.LogWarning("username may not be empty!");
            return null;
        }

        if (!_users.ContainsKey(username))
        {
            Debug.LogWarning($"A user with name \"{username}\" does not exist!");
            return null;
        }

        return _users[username];
    }

    public static bool LogIn(string username, string password)
    {
        var user = GetUserByName(username);
        return user == null ? false : user.CheckPassword(password);
    }

    public static void AddUser(string username, string password, Roles role)
    {
        // Check the name
        if (string.IsNullOrWhiteSpace(username))
        {
            Debug.LogWarning("username may not be empty!");
            return;
        }

        if (_users.ContainsKey(username))
        {
            Debug.LogWarning($"A user with name \"{username}\" already exists! Chose another username!");
            return;
        }

        _users.Add(username, new User(username, password, role));

        SaveUsers();
    }

    public static void FactorySettings()
    {
        Debug.Log("FactorySettings!");

        _users.Clear();
        SaveUsers();
    }
}

[Serializable]
public class User
{
    public string Name;
    public string PasswordHash;
    public Roles Role;


    public User(string name, string password, Roles role)
    {
        Name = name;

        // Never store a naked Password!
        // Rather store the hash and everytime you want to check the password
        // compare the hashes!
        PasswordHash = GetHash(password);

        // As said actually you should also not store this as a simple modifiable value
        // Since it almost has the same security impact as a password!
        Role = role;
    }

    private static string GetHash(string password)
    {
        var unhashedBytes = Encoding.Unicode.GetBytes(password);

        var sha256 = new SHA256Managed();
        var hashedBytes = sha256.ComputeHash(unhashedBytes);

        return Convert.ToBase64String(hashedBytes);
    }

    public bool CheckPassword(string attemptedPassword)
    {
        var base64AttemptedHash = GetHash(attemptedPassword);

        return base64AttemptedHash.Equals(PasswordHash);
    }
}

public enum Roles
{
    Teacher,
    Student
}

Я знаю, BinaryFormatter создает довольно большие файлы, и выМожно также сделать это, например, как JSON, но это был самый простой способ показать, как (де) сериализовать Dictionary в системный файл.


И только для небольшого демонстрационного класса

using UnityEditor;
using UnityEngine;

public class UsersManager : MonoBehaviour
{
    public string username;
    public string password;
    public Roles role;

    public User User;
}

[CustomEditor(typeof(UsersManager))]
public class UsersManagerEditor : Editor
{
    private UsersManager manager;

    private void OnEnable()
    {
        manager = (UsersManager)target;
    }

    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();

        if (GUILayout.Button("GetUserByName"))
        {
            manager.User = DataBase.GetUserByName(manager.username);
        }

        if (GUILayout.Button("AddUser"))
        {
            DataBase.AddUser(manager.username, manager.password, manager.role);
        }

        if (GUILayout.Button("CheckPassword"))
        {
            manager.User = DataBase.GetUserByName(manager.username);

            if (manager.User != null)
            {

                if (manager.User.CheckPassword(manager.password))
                {
                    Debug.Log("Password CORRECT!");
                }
                else
                {
                    Debug.LogWarning("PASSWORD WRONG!");
                }
            }
        }

        if (GUILayout.Button("FactorySettings"))
        {
            DataBase.FactorySettings();
        }
    }
}

enter image description here

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...