Adicionando SSO após muitos usuários já terem se cadastrado -- como migrá-los?

Olá a todos.

Qual é a maneira correta de importar todos os usuários existentes do Discourse para o intercoin.app a partir do Discourse? Existe algum tipo de endpoint REST que retorne todos os usuários com suas senhas com hash e salts, entre outras coisas? Qual é o link para o algoritmo de hash no github? Terei que codificar do nosso lado, para usar o mesmo algoritmo de hash com a senha e o salt inseridos, caso o nosso próprio não funcione, para permitir que esses caras façam login. Acho que #2 é relevante sempre que um usuário do Discourse ativar o SSO mais tarde (como nós), então resolvê-lo ajudaria outros usuários do Discourse também.

2 curtidas

Abordagem interessante.

Em user.rb

  def confirm_password?(password)
    return false unless password_hash && salt
    self.password_hash == hash_password(password, salt)
  end

  def hash_password(password, salt)
    raise StandardError.new("a senha é muito longa") if password.size > User.max_password_length
    Pbkdf2.hash_password(password, salt, Rails.configuration.pbkdf2_iterations, Rails.configuration.pbkdf2_algorithm)
  end

e então o código Pbkdf2 está aqui: discourse/lib/pbkdf2.rb at 201228162c277b9833bb2988388553fdbfb39521 · discourse/discourse · GitHub

2 curtidas

Excelente! Agora, qual é o endpoint HTTP que eu chamo para obter todas as informações do usuário, incluindo hash de senha e salt?

Imagino que não haja um para servir ao público em geral (por que facilitar o hacking de pessoas?) Então, o que posso fazer? Conectar ao banco de dados MySQL? Escrever um plugin do Discourse?

1 curtida

Essas são basicamente suas opções, sim.

1 curtida

O esquema do banco de dados está documentado em algum lugar?

Como me conectar ao banco de dados postgres no docker? Desculpe se for uma pergunta boba.

1 curtida

Acabei de falar com nossa equipe, e eles concordam, estou preparado para pagar alguém para criar um pequeno plugin do Discourse que exponha via endpoint algumas informações JSON sobre um usuário, dado seu endereço de e-mail.

Encontrei discourse/app/models/user.rb at main · discourse/discourse · GitHub que tem “password_hash”, salt e nome, username. Mas não tem e-mail. Para isso, vejo user_email discourse/app/models/user_email.rb at main · discourse/discourse · GitHub

Então, dado um e-mail, o plugin simplesmente pesquisará na tabela user_email pelo e-mail, depois encontrará o user_id e pegará a linha do usuário, e enviará todos os campos “seguros”, incluindo o salt.

Para segurança extra, as requisições podem ser assinadas via HMAC usando um segredo compartilhado que pode ser fornecido ao plugin.

Alguém quer fazer isso? Me mande uma mensagem ou responda aqui e me diga como entrar em contato. Espero que seja simples (alguns SELECTs e uma verificação de HMAC se o segredo foi definido). Leremos o JSON.

1 curtida

Eu apenas usaria o admin/users/list/active.json existente e estenderia a resposta com as senhas hasheadas.

Além disso, mantenha o mecanismo de autenticação de API existente, não reinvente outra roda.

1 curtida

Então você está dizendo para ter uma coisa única que importe todos os usuários que entraram com todos os seus salts e senhas?

Tudo bem, mas isso ainda precisa ser um plugin, não é? Então seria ótimo se alguém do Discourse pudesse criar isso.

1 curtida

Eu provavelmente usaria o plugin data explorer para exportar as informações que você deseja. Será muito mais fácil do que escrever um novo plugin.

1 curtida

Como descubro esse valor de configuração? Qual é o padrão, está em algum lugar no código? @RGJ

root@server:~# cd /var/discourse/
root@server:/var/discourse# ./launcher enter app
x86_64 arch detectado.
root@server:/var/www/discourse# rails c
[1] pry(main)> Rails.configuration.pbkdf2_iterations
=> 64000
[2] pry(main)>

Obrigado! OK @RGJ, algumas perguntas rápidas:

A biblioteca xorcist é apenas um xor de string mais rápido, certo? E se um dos caracteres acabar sendo 0 porque ‘a’ foi xorado com ‘a’ — o que acontece com essa string? As strings não são terminadas em nulo?

Meu objetivo é portar isso para PHP, então qualquer coisa que você puder fazer para ajudar (como me dar informações sobre como replicá-lo em PHP) será muito útil.

Além disso, o que essa linha está fazendo? ret.bytes.map { |b| (\"0\" + b.to_s(16))[-2..-1] }.join(\"\")

$u = hash_hmac('sha256', $password, $salt . pack('N', 1));
$ret = $u = hash_hmac('sha256', $password, $u);
for ($i=2; $i<$iterations; ++$i) {
  $u = hash_hmac('sha256', $password, $u);
  $ret = ($ret ^ $u);
}
// todo: descobrir o que o RUBY está fazendo nesta última linha

Isso está perto? Você pode corrigir este código PHP?

Esta é uma função integrada.

$hash = hash_pbkdf2('sha256', 'SuaSenha', 'SeuSalt', 64000, 64, false);

Normalmente, os algoritmos de hash operam em dados binários e o resultado é codificado em hexadecimal ou Base64 na saída. Portanto, isso não é um problema.

1 curtida

Muito obrigado, Richard! Você me economizou MUITO tempo tendo que implementá-lo em PHP do lado do usuário!

Sim! Consegui criar um script que percorre todos os usuários do Discourse e os importa com o hash de sua senha em nossa plataforma.

Em breve, poderemos permitir que qualquer pessoa que tenha um fórum Discourse adicione também Eventos, Videoconferência, Mídia e mais, com o Discourse residindo na aba “Discuss”. Você pode ver o resultado em https://intercoin.app

Basicamente, transformando qualquer instalação do Discourse em uma rede social moderna, à la Facebook. Trabalhamos por anos nesses recursos e agora queremos integrá-los de perto com o Discourse e o WordPress também. Assim, as pessoas podem combinar WordPress, Discourse e Qbix e auto-hospedar toda a sua comunidade.

Mas tenho duas questões restantes.

  1. No Qbix, nós fazemos o hash da senha no cliente, pelo menos com sha1(senha + userId) antes de enviá-la ao servidor. Mesmo quando é https. Fazemos isso para que o servidor ou qualquer MITM NUNCA tenha a senha, para reutilizá-la em vários sites. Mas o Discourse simplesmente envia a senha para o servidor. Então, tivemos que desativar esse hash no lado do cliente. É possível fazer algumas iterações de hash_pbkdf2 no lado do cliente e o restante no lado do servidor? Tentei e não parece alinhar:
php > $password = 'abc';
php > $salt = 'def';
php > $a = hash_pbkdf2('sha256', $password, $salt, 64000, 64, false);
php > $b = hash_pbkdf2('sha256', $password, $salt, 1, 64, false);
php > $c = hash_pbkdf2('sha256', $password, $b, 63999, 64, false);
php > echo $a;
9d7a21ae4113bea06d81e0c486f45ab778bb739f19f7a6a305d8401918a9d8a1
php > echo $c;
f42af6861ebcf8560b027276e0d02ad46502636045486057d81be7c4c4aa630e
  1. Seria possível usar o Discourse apenas como um Provedor de SSO, em vez de usar nosso site como provedor de SSO? Então, os hosts de fóruns Discourse seriam ainda mais propensos a expandi-lo com recursos do Qbix, já que o login permaneceria exatamente o mesmo, e do lado do Discourse. Facebook, Google e o que mais. Existe alguma documentação sobre que tipo de informação o Discourse Connect retorna como Provedor de SSO para nosso site consumidor? Ele inclui coisas como a foto que podemos baixar, primeiro nome, último nome e nome de usuário, pelo menos?

Sinceramente, não acho que o Discourse enviar uma senha via HTTPS seja seu maior desafio de segurança neste momento.

Claro. Acho que você obtém a maioria das coisas no serializador de usuário padrão.
Mas se isso não for suficiente, você sempre pode usar a API para obter mais informações do Discourse.

2 curtidas

\u003e Sinceramente, não acho que o envio de uma senha via HTTPS pelo Discourse seja seu maior desafio de segurança neste momento.

Fofo. Vejo seu sha1 e te dou um md5 :slight_smile:

Entendo por que esse pbkdf2 não funciona realmente para dividi-lo… o problema é a primeira linha:

U1 = PRF(Password, Salt + INT_32_BE(i))
U2 = PRF(Password, U1)
⋮
Uc = PRF(Password, Uc−1)

Alguma ideia de como dividi-lo, então? Acho que posso usar a biblioteca php pura do userland: pbkdf2/src/PBKDF2.php at master · Spomky-Labs/pbkdf2 · GitHub

Eu recomendaria ao Discourse que hasheasse as coisas com um salt (userId funciona) antes de enviar a senha pela rede. Por que não? Não precisa se tornar incompatível com o que você está armazenando no banco de dados agora. Simplesmente faça as primeiras 100 iterações em Javascript, depois subtraia 10 de 64000. Você tem uma implementação personalizada de qualquer maneira (copiada do rails), então você apenas enviaria uma variável isHashed, e se for verdadeira, então faça apenas os “últimos” 64K-10 passos.

O ID do usuário não é conhecido antes do login, então isso não funcionará…

10 iterações não são seguras, e 63990 iterações são menos seguras do que 64000 iterações. Portanto, embora seja marginal, parece que você está substituindo um método seguro por dois métodos menos seguros e muita complexidade extra.

E qual é o ganho real?

1 curtida