/*
TShock, a server mod for Terraria
Copyright (C) 2011 The TShock Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
using System;
using System.Data;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using MySql.Data.MySqlClient;
using System.Text.RegularExpressions;
namespace TShockAPI.DB
{
public class UserManager
{
private IDbConnection database;
public UserManager(IDbConnection db)
{
database = db;
var table = new SqlTable("Users",
new SqlColumn("ID", MySqlDbType.Int32) {Primary = true, AutoIncrement = true},
new SqlColumn("Username", MySqlDbType.VarChar, 32) {Unique = true},
new SqlColumn("Password", MySqlDbType.VarChar, 128),
new SqlColumn("Usergroup", MySqlDbType.Text),
new SqlColumn("IP", MySqlDbType.VarChar, 16)
);
var creator = new SqlTableCreator(db,
db.GetSqlType() == SqlType.Sqlite
? (IQueryBuilder) new SqliteQueryCreator()
: new MysqlQueryCreator());
creator.EnsureExists(table);
}
///
/// Adds a given username to the database
///
/// User user
public void AddUser(User user)
{
if (!TShock.Groups.GroupExists(user.Group))
throw new GroupNotExistsException(user.Group);
int ret;
try
{
ret = database.Query("INSERT INTO Users (Username, Password, UserGroup, IP) VALUES (@0, @1, @2, @3);", user.Name,
TShock.Utils.HashPassword(user.Password), user.Group, user.Address);
}
catch (Exception ex)
{
// Detect duplicate user using a regexp as Sqlite doesn't have well structured exceptions
if (Regex.IsMatch(ex.Message, "Username.*not unique"))
throw new UserExistsException(user.Name);
throw new UserManagerException("AddUser SQL returned an error (" + ex.Message + ")", ex);
}
if (1 > ret)
throw new UserExistsException(user.Name);
}
///
/// Removes a given username from the database
///
/// User user
public void RemoveUser(User user)
{
try
{
int affected = -1;
if (!string.IsNullOrEmpty(user.Address))
{
affected = database.Query("DELETE FROM Users WHERE IP=@0", user.Address);
}
else
{
affected = database.Query("DELETE FROM Users WHERE Username=@0", user.Name);
}
if (affected < 1)
throw new UserNotExistException(string.IsNullOrEmpty(user.Address) ? user.Name : user.Address);
}
catch (Exception ex)
{
throw new UserManagerException("RemoveUser SQL returned an error", ex);
}
}
///
/// Sets the Hashed Password for a given username
///
/// User user
/// string password
public void SetUserPassword(User user, string password)
{
try
{
if (
database.Query("UPDATE Users SET Password = @0 WHERE Username = @1;", TShock.Utils.HashPassword(password),
user.Name) == 0)
throw new UserNotExistException(user.Name);
}
catch (Exception ex)
{
throw new UserManagerException("SetUserPassword SQL returned an error", ex);
}
}
///
/// Sets the group for a given username
///
/// User user
/// string group
public void SetUserGroup(User user, string group)
{
try
{
Group grp = TShock.Groups.GetGroupByName(group);
if (null == grp)
throw new GroupNotExistsException(group);
if (database.Query("UPDATE Users SET UserGroup = @0 WHERE Username = @1;", group, user.Name) == 0)
throw new UserNotExistException(user.Name);
// Update player group reference for any logged in player
foreach (var player in TShock.Players.Where(p => null != p && p.UserAccountName == user.Name))
{
player.Group = grp;
}
}
catch (Exception ex)
{
throw new UserManagerException("SetUserGroup SQL returned an error", ex);
}
}
public int GetUserID(string username)
{
try
{
using (var reader = database.QueryReader("SELECT * FROM Users WHERE Username=@0", username))
{
if (reader.Read())
{
return reader.Get("ID");
}
}
}
catch (Exception ex)
{
Log.ConsoleError("FetchHashedPasswordAndGroup SQL returned an error: " + ex);
}
return -1;
}
///
/// Returns a Group for a ip from the database
///
/// string ip
public Group GetGroupForIP(string ip)
{
try
{
using (var reader = database.QueryReader("SELECT * FROM Users WHERE IP=@0", ip))
{
if (reader.Read())
{
string group = reader.Get("UserGroup");
return TShock.Utils.GetGroup(group);
}
}
}
catch (Exception ex)
{
Log.ConsoleError("GetGroupForIP SQL returned an error: " + ex);
}
return TShock.Utils.GetGroup(TShock.Config.DefaultGuestGroupName);
}
public Group GetGroupForIPExpensive(string ip)
{
try
{
using (var reader = database.QueryReader("SELECT IP, UserGroup FROM Users"))
{
while (reader.Read())
{
if (TShock.Utils.GetIPv4Address(reader.Get("IP")) == ip)
{
return TShock.Utils.GetGroup(reader.Get("UserGroup"));
}
}
}
}
catch (Exception ex)
{
Log.ConsoleError("GetGroupForIP SQL returned an error: " + ex);
}
return TShock.Utils.GetGroup(TShock.Config.DefaultGuestGroupName);
}
public User GetUserByName(string name)
{
try
{
return GetUser(new User {Name = name});
}
catch (UserManagerException)
{
return null;
}
}
public User GetUserByID(int id)
{
try
{
return GetUser(new User {ID = id});
}
catch (UserManagerException)
{
return null;
}
}
public User GetUserByIP(string ip)
{
try
{
return GetUser(new User {Address = ip});
}
catch (UserManagerException)
{
return null;
}
}
public User GetUser(User user)
{
try
{
QueryResult result;
if (0 != user.ID)
result = database.QueryReader("SELECT * FROM Users WHERE ID=@0", user.ID);
else if (string.IsNullOrEmpty(user.Address))
result = database.QueryReader("SELECT * FROM Users WHERE Username=@0", user.Name);
else
result = database.QueryReader("SELECT * FROM Users WHERE IP=@0", user.Address);
if (result.Read())
{
user = LoadUserFromResult(user, result);
// Check for multiple matches
if (!result.Read())
return user;
if (0 != user.ID)
throw new UserManagerException(String.Format("Multiple users found for id '{0}'", user.ID));
else if (string.IsNullOrEmpty(user.Address))
throw new UserManagerException(String.Format("Multiple users found for name '{0}'", user.Name));
else
throw new UserManagerException(String.Format("Multiple users found for ip '{0}'", user.Address));
}
}
catch (Exception ex)
{
throw new UserManagerException("GetUser SQL returned an error (" + ex.Message + ")", ex);
}
throw new UserNotExistException(string.IsNullOrEmpty(user.Address) ? user.Name : user.Address);
}
public List GetUsers()
{
try
{
List users = new List();
using (var reader = database.QueryReader("SELECT * FROM Users"))
{
while (reader.Read())
{
users.Add(LoadUserFromResult(new User(), reader));
}
return users;
}
}
catch (Exception ex)
{
Log.Error(ex.ToString());
}
return null;
}
private User LoadUserFromResult(User user, QueryResult result)
{
user.ID = result.Get("ID");
user.Group = result.Get("Usergroup");
user.Password = result.Get("Password");
user.Name = result.Get("Username");
user.Address = result.Get("IP");
return user;
}
}
public class User
{
public int ID { get; set; }
public string Name { get; set; }
public string Password { get; set; }
public string Group { get; set; }
public string Address { get; set; }
public User(string ip, string name, string pass, string group)
{
Address = ip;
Name = name;
Password = pass;
Group = group;
}
public User()
{
Address = "";
Name = "";
Password = "";
Group = "";
}
}
[Serializable]
public class UserManagerException : Exception
{
public UserManagerException(string message)
: base(message)
{
}
public UserManagerException(string message, Exception inner)
: base(message, inner)
{
}
}
[Serializable]
public class UserExistsException : UserManagerException
{
public UserExistsException(string name)
: base("User '" + name + "' already exists")
{
}
}
[Serializable]
public class UserNotExistException : UserManagerException
{
public UserNotExistException(string name)
: base("User '" + name + "' does not exist")
{
}
}
[Serializable]
public class GroupNotExistsException : UserManagerException
{
public GroupNotExistsException(string group)
: base("Group '" + group + "' does not exist")
{
}
}
}