Sesión 3 finalizada

parent b8b96013
Showing with 328 additions and 172 deletions
# Primeros pasos en Solid
Vamos a implementar una aplicación simple en SOLID, en la que vamos a poder autenticarnos y luego ver información de un usuario.
## Requisitos
Todo el software necesario lo tenéis en este fichero, pero para poder ejecutar la aplicación es necesario que esté servida por web. Para ello debemos abrir la aplicación desde un servidor web, que puede ser local, con una instalación propia de Apache o NGINX. Si trabajáis con Linux es tan sencillo como instalar Apache, si estáis en Windows podéis optar por Wampserver o Xamp.
Ubicad estos archivos en el directorio donde se alojarían las páginas web públicas, bajo otro directorio denomiando "solid".
## Crea tu POD
Antes de poder probar la autenticación, es necesario que dispongas de una cuenta registrada en algún servidor que soporte autenticación SOLID. Una opción es https://solid.community. Créate una cuenta ahí y así tendras credenciales y un POD ("vaina" en inglés). Tu pod es el lugar donde mantienes todos tus datos personales, es tu espacio de almacenamiento privado.
## Funcionamiento del código
Al abrir la dirección http://localhost/solid/index.hmtl, tienes la opción de iniciar sesión. Se abrirá otra ventana para autenticarte con alguno de los servicios disponibles. Si te registraste en solid.community, selecciona ese botón y introduce tus credenciales.
La página debería cambiar, mostrando tu WebID y un nuevo botón para cerrar sesión.
¡Ya está! Ya tienes una primera aplicación capaz de autenticar usuarios.
\ No newline at end of file
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Navegador SOLID</title>
<meta charset="utf-8">
<title>Navegador SOLID</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<link rel="stylesheet" href="css/main.css">
</head>
<style>
body {
max-width: 800px;
margin: 0 auto;
}
<body>
<nav class="navbar navbar-light bg-light">
<a class="navbar-brand" href="#">
<img src="https://solidproject.org/assets/img/solid-emblem.svg" width="30" height="30" class="d-inline-block align-top" alt="">
Navegador SOLID
</a>
<div class="out-session">
<button id="loginButton" class="btn btn-outline-success my-2 my-sm-0" type="submit">Iniciar sesión</button>
</div>
<div class="in-session">
<strong id="user"></strong>
<button id="logoutButton" class="btn btn-outline-danger my-2 my-sm-0" type="submit">Cerrar sesión</button>
</div>
</nav>
body,
input,
button {
font: 11pt/1.3 "Helvetiva Neue", Helvetica, Arial, sans-serif;
}
<div class="container in-session">
<div class="row">
button,
label {
font-weight: bold;
}
<div class="col-md-10 offset-md-1">
<h2>Perfil</h2>
<div class="input-group mb-3">
<input id="webid" class="form-control">
<div class="input-group-append">
<button id="view" class="btn btn-primary">Mostrar</button>
</div>
</div>
</div>
</div>
#profile {
width: 400px;
}
<div class="row">
<div id="photo" class="col-md-2 offset-md-1"></div>
<div class="col-md-2">
<h4 id="fullName"></h4>
<span id="email"></span>
</div>
<div class="col-md-4 offset-md-1">
<h4>Contactos</h4>
<ul id="friends"></ul>
<div class="input-group mb-3">
<input id="newFriend" class="form-control" placeholder="Introduce WebID">
<div class="input-group-append">
<button id="addFriend" class="btn btn-success"><svg class="bi bi-person-plus" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M11 14s1 0 1-1-1-4-6-4-6 3-6 4 1 1 1 1h10zm-9.995-.944v-.002.002zM1.022 13h9.956a.274.274 0 00.014-.002l.008-.002c-.001-.246-.154-.986-.832-1.664C9.516 10.68 8.289 10 6 10c-2.29 0-3.516.68-4.168 1.332-.678.678-.83 1.418-.832 1.664a1.05 1.05 0 00.022.004zm9.974.056v-.002.002zM6 7a2 2 0 100-4 2 2 0 000 4zm3-2a3 3 0 11-6 0 3 3 0 016 0zm4.5 0a.5.5 0 01.5.5v2a.5.5 0 01-.5.5h-2a.5.5 0 010-1H13V5.5a.5.5 0 01.5-.5z" clip-rule="evenodd"/>
<path fill-rule="evenodd" d="M13 7.5a.5.5 0 01.5-.5h2a.5.5 0 010 1H14v1.5a.5.5 0 01-1 0v-2z" clip-rule="evenodd"/>
</svg></button>
</div>
</div>
</div>
</div>
dt {
font-weight: bold;
clear: left;
float: left;
}
<div class="row">
<div class="col-md-10 offset-md-1">
<h4>Almacenamiento</h4>
<div class="input-group mb-3">
<input id="container" class="form-control">
<div class="input-group-append">
<button id="updateContainer" class="btn btn-primary">Actualizar</button>
</div>
</div>
</div>
</div>
dt:after {
content: ': ';
}
<div class="row">
<div class="col-md-5 offset-md-1">
<ul id="items"></ul>
</div>
<div class="col-md-3 offset-md-1">
<label>Crear nuevo recurso:</label>
a {
color: #7c4dff;
border-bottom: 1px solid;
cursor: pointer;
}
<div class="input-group mb-3">
<input id="newContainer" class="form-control" placeholder="Nombre de la carpeta">
<div class="input-group-append">
<button id="addContainer" class="btn btn-success"><svg class="bi bi-folder-plus" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M9.828 4H2.19a1 1 0 00-.996 1.09l.637 7a1 1 0 00.995.91H9v1H2.826a2 2 0 01-1.991-1.819l-.637-7a1.99 1.99 0 01.342-1.31L.5 3a2 2 0 012-2h3.672a2 2 0 011.414.586l.828.828A2 2 0 009.828 3h3.982a2 2 0 011.992 2.181L15.546 8H14.54l.265-2.91A1 1 0 0013.81 4H9.828zm-2.95-1.707L7.587 3H2.19c-.24 0-.47.042-.684.12L1.5 2.98a1 1 0 011-.98h3.672a1 1 0 01.707.293z" clip-rule="evenodd"/>
<path fill-rule="evenodd" d="M13.5 10a.5.5 0 01.5.5v2a.5.5 0 01-.5.5h-2a.5.5 0 010-1H13v-1.5a.5.5 0 01.5-.5z" clip-rule="evenodd"/>
<path fill-rule="evenodd" d="M13 12.5a.5.5 0 01.5-.5h2a.5.5 0 010 1H14v1.5a.5.5 0 01-1 0v-2z" clip-rule="evenodd"/>
</svg></button>
</div>
</div>
a:hover {
color: #9b79fc;
}
</style>
<div class="input-group mb-3">
<input id="newRdf" class="form-control" placeholder="Nombre del archivo .ttl">
<div class="input-group-append">
<button id="addRdf" class="btn btn-success"><svg class="bi bi-file-earmark-plus" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path d="M9 1H4a2 2 0 00-2 2v10a2 2 0 002 2h5v-1H4a1 1 0 01-1-1V3a1 1 0 011-1h5v2.5A1.5 1.5 0 0010.5 6H13v2h1V6L9 1z"/>
<path fill-rule="evenodd" d="M13.5 10a.5.5 0 01.5.5v2a.5.5 0 01-.5.5h-2a.5.5 0 010-1H13v-1.5a.5.5 0 01.5-.5z" clip-rule="evenodd"/>
<path fill-rule="evenodd" d="M13 12.5a.5.5 0 01.5-.5h2a.5.5 0 010 1H14v1.5a.5.5 0 01-1 0v-2z" clip-rule="evenodd"/>
</svg></button>
</div>
</div>
<body>
<h1>Navegador SOLID</h1>
<div id="login">
<button>Iniciar sesión</button>
</div>
<div id="logout">
Has iniciado sesión como <strong id="user"></strong>.
<button id="logoutButton">Cerrar sesión</button>
<h2>Perfil</h2>
<input id="webid" size="60"> <button id="view">Mostrar</button>
<p>
<label>Nombre:</label>
<span id="fullName"></span>
</p>
<p id="photo"></p>
<label>Eliminar recurso:</label>
<div class="input-group mb-3">
<input id="deleteUri" class="form-control" placeholder="URI del recurso">
<div class="input-group-append">
<button id="delete" class="btn btn-danger"><svg class="bi bi-file-earmark-minus" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path d="M9 1H4a2 2 0 00-2 2v10a2 2 0 002 2h5v-1H4a1 1 0 01-1-1V3a1 1 0 011-1h5v2.5A1.5 1.5 0 0010.5 6H13v2h1V6L9 1z"/>
<path fill-rule="evenodd" d="M11 11.5a.5.5 0 01.5-.5h4a.5.5 0 010 1h-4a.5.5 0 01-.5-.5z" clip-rule="evenodd"/>
</svg></button>
</div>
</div>
<h2>Contactos</h2>
<ul id="friends"></ul>
<input id="newFriend" size="60"/> <button id="addFriend">Añadir</button>
<label>Hacer privado:</label>
<div class="input-group mb-3">
<input id="blockUri" class="form-control" placeholder="URI del recurso">
<div class="input-group-append">
<button id="block" class="btn btn-warning"><svg class="bi bi-lock" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M11.5 8h-7a1 1 0 00-1 1v5a1 1 0 001 1h7a1 1 0 001-1V9a1 1 0 00-1-1zm-7-1a2 2 0 00-2 2v5a2 2 0 002 2h7a2 2 0 002-2V9a2 2 0 00-2-2h-7zm0-3a3.5 3.5 0 117 0v3h-1V4a2.5 2.5 0 00-5 0v3h-1V4z" clip-rule="evenodd"/>
</svg></button>
</div>
</div>
<h2>POD</h2>
<input id="container" size="60" /> <button id="updateContainer">Actualizar</button>
<ul id="items"></ul>
<input id="newContainer" size="60"/> <button id="addContainer">Crear carpeta</button>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="js/solid-auth-client.bundle.js"></script>
<script src="https://cdn.jsdelivr.net/npm/rdflib@1.1.0/dist/rdflib.min.js"></script>
<script src="js/main.js"></script>
</body>
</div> <!-- / .container -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="js/solid-auth-client.bundle.js"></script>
<script src="https://cdn.jsdelivr.net/npm/solid-file-client@1.0.0/dist/window/solid-file-client.bundle.js"></script>
<script src="https://cdn.jsdelivr.net/npm/rdflib@1.1.0/dist/rdflib.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
<script src="js/main.js"></script>
</body>
</html>
\ No newline at end of file
/* ------------------------------------
* Gestión de sesiones
* ------------------------------------ */
$('#login button').click(() => popupLogin());
$('#logout #logoutButton').click(() => solid.auth.logout());
/* Login */
async function popupLogin() {
let session = await solid.auth.currentSession();
let popupUri = 'https://solidcommunity.net/common/popup.html';
if (!session)
session = await solid.auth.popupLogin({ popupUri });
}
solid.auth.trackSession(session => {
const loggedIn = !!session;
$('#login').toggle(!loggedIn);
$('#logout').toggle(loggedIn);
if (session) {
$('#user').text(session.webId);
if (!$('#webid').val())
$('#webid').val(session.webId);
}
});
/* ------------------------------------
* Recuperación de datos
* ------------------------------------ */
/* -----------------------------------------
Inicialización
*/
// Creamos un almacén y le asociamos un lector RDF
const store = $rdf.graph();
const fetcher = new $rdf.Fetcher(store);
// Establecemos espacios de nombres
const FOAF = $rdf.Namespace('http://xmlns.com/foaf/0.1/');
const VCARD = $rdf.Namespace('http://www.w3.org/2006/vcard/ns#');
const LDP = $rdf.Namespace('http://www.w3.org/ns/ldp#');
const DC = $rdf.Namespace('http://purl.org/dc/terms/');
const ACL = $rdf.Namespace('http://www.w3.org/ns/auth/acl#');
// Creamos un almacén y le asociamos un lector RDF
const store = $rdf.graph();
const fetcher = new $rdf.Fetcher(store);
// Definimos función para invocar todas subvistas
function showProfile(person) {
showPersonalData(person);
showFriends(person);
var patt = /https:\/\/[^\/]+\//;
const rootContainer = patt.exec(person) + 'public/';
showContainer(rootContainer);
}
/* ---------------------------------------
Gestión de sesiones
*/
const popupUri = 'https://solidcommunity.net/common/popup.html';
localStorage.clear();
$('#loginButton').click(() => solid.auth.popupLogin({ popupUri }));
$('#logoutButton').click(() => solid.auth.logout());
const fc = new SolidFileClient(solid.auth);
solid.auth.trackSession(async session => {
const loggedIn = !!session;
$('.out-session').toggle(!loggedIn);
$('.in-session').toggle(loggedIn);
if (session) {
$('#user').text(session.webId);
$('#webid').val(session.webId);
await fetcher.load(session.webId);
showProfile(session.webId);
}
});
$('#view').click(() => showProfile());
$('#updateContainer').click(() => showContainer($('#container').val()));
/* --------------
Mostramos información del perfil referenciado por la WebID
/* ------------------------------------
Información del perfil
*/
async function showProfile() {
// Cargamos los datos a partir del WebID
const person = $('#webid').val();
await fetcher.load(person);
showPersonalData(person);
showFriends(person);
var patt = /https:\/\/[^\/]+\//;
const rootContainer = patt.exec(person) + 'public/';
showContainer(rootContainer);
}
function showPersonalData(person) {
// Mostramos tres campos del perfil
const fullName = store.any($rdf.sym(person), FOAF('name'));
const photoUrl = store.any($rdf.sym(person), VCARD('hasPhoto'));
const email_node = store.any($rdf.sym(person), VCARD('hasEmail'));
const email = store.any(email_node, VCARD('value'));
// Información personal
function showPersonalData(person) {
// Mostramos tres campos del perfil
const fullName = store.any($rdf.sym(person), VCARD('fn'));
const photoUrl = store.any($rdf.sym(person), VCARD('hasPhoto'));
$('#fullName').text(fullName && fullName.value);
$('#photo').html($('<img>').attr('src', photoUrl && photoUrl.value).attr('width', '200px'));
$('#fullName').text(fullName && fullName.value);
$('#email').html($('<a>').attr('href', email && email.value).text(email && email.value));
$('#photo').html($('<img>').attr('src', photoUrl && photoUrl.value).attr('width', '160px'));
}
/* -------------------------
/* ------------------------------------
Contactos (navegable)
*/
async function showFriends(person) {
// Mostramos su lista de amigos
const friends = store.each($rdf.sym(person), FOAF('knows'));
$('#friends').empty();
friends.forEach(async (friend) => {
await fetcher.load(friend); // con cada "load", la base de conocimiento ¡crece!
const fullName = store.any(friend, FOAF('name'));
$('#friends').append(
$('<li>').append(
$('<a>').text((fullName && fullName.value || friend.value))
.click(() => $('#webid').val(friend.value))
.click(() => showProfile(friend.value))));
});
// Mostramos su lista de amigos
const friends = store.each($rdf.sym(person), FOAF('knows'));
$('#friends').empty();
friends.forEach(async (friend) => {
await fetcher.load(friend); // cada vez que hacemos "load", nuestra base de conocimiento ¡crece!
const fullName = store.any(friend, FOAF('name'));
$('#friends').append(
$('<li>').append(
$('<button>').text((fullName && fullName.value || friend.value))
.addClass('btn btn-link')
.click(() => $('#webid').val(friend.value))
.click(() => showProfile(friend.value))));
});
}
/* -----------------------------------------------------
Cargamos perfil
*/
$('#view').click(async function () {
// Cargamos los datos a partir del WebID
const person = $('#webid').val();
await fetcher.load(person);
showProfile(person);
});
/** -----------------------------------------------
* Añadimos un nuevo amigo
*/
$('#addFriend').click(async function () {
person = store.sym($('#webid').val()); // nodos
friend = store.sym($('#newFriend').val());
profile = person.doc(); // este es el documento a actualizar (card)
store.add(person, FOAF('knows'), friend, profile);
await fetcher.putBack(profile); // actualizamos documento en la nube
$('#newFriend').val('');
showFriends(person);
});
/* -----------------------------------------------
Contenido del POD (navegable)
Contenido del POD (navegable)
*/
async function showContainer(container) {
// Cargamos datos desde el POD
await fetcher.load(container);
$('#container').val(container);
// Mostramos elementos
const items = store.each($rdf.sym(container), LDP('contains'));
$('#items').empty();
items.forEach(async (item) => {
if (item.value[item.value.length - 1] == '/')
element = $('<a>').text(decodeURI(item.value))
.click(() => $('#container').val(item.value))
.click(() => showContainer(item.value));
else
element = $('<a>').text(decodeURI(item.value))
.attr('href', item.value)
.attr('target', 'blank');
$('#items').append($('<li>').append(element));
});
// Cargamos el contenido del POD
await fetcher.load(container);
$('#container').val(container);
// Mostramos elementos
const items = store.each($rdf.sym(container), LDP('contains'));
$('#items').empty();
items.forEach(async (item) => {
text = item.value.replace(container, ''); // eliminamos ruta
if (item.value[item.value.length-1] == '/')
element = $('<button>').text(decodeURI(text))
.addClass('btn btn-outline-primary')
.click(() => $('#container').val(item.value))
.click(() => showContainer(item.value));
else
element = $('<a>').text(decodeURI(text))
.addClass('btn btn-link')
.attr('href', item.value)
.attr('target', 'blank');
$('#items').append($('<li>').append(element));
});
}
/* --------------------------------------------------
Añadimos una nueva carpeta (contenedor)
/*-------------------------------------------------------
Actualizamos POD
*/
$('#addContainer').click(async function () {
await fetcher.createContainer($('#container').val(), $('#newContainer').val(), null);
$('#newContainer').val('');
showContainer($('#container').val());
$('#updateContainer').click(async function () {
fetcher.refresh($('#container').val())
showContainer($('#container').val());
});
/* -----------------------------------------------
* Añadimos un nuevo amigo
/* --------------------------------------------------
Añadimos una nueva carpeta (contenedor)
*/
$('#addFriend').click(async function () {
person = store.sym($('#webid').val()); // nodos
friend = store.sym($('#newFriend').val());
profile = person.doc(); // este es el documento a actualizar (card)
store.add(person, FOAF('knows'), friend, profile);
await fetcher.putBack(profile); // actualizamos documento en la nube
$('#newFriend').val('');
showFriends(person);
$('#addContainer').click(async function () {
await fetcher.createContainer($('#container').val(), $('#newContainer').val(), null);
$('#newContainer').val('');
fetcher.refresh($('#container').val())
showContainer($('#container').val());
});
/* --------------------------------------------------
Consultas con SPARQL
*/
/*
Para probar en la consola
// ¿Quién conoce al usuario con este WebId?
sparql_query = " \
PREFIX foaf: <http://xmlns.com/foaf/0.1/> \
SELECT ?who WHERE { \
?who foaf:knows <" + $('#webid').val() + "> . \
}";
// Generamos consulta para rdflib
query = $rdf.SPARQLToQuery(sparql_query, false, store);
query_sparql = "PREFIX foaf: <http://xmlns.com/foaf/0.1/> \
SELECT ?who WHERE { \
?who foaf:knows <" . $(#webid).val() . " > . \
}";
// Ejecutamos consulta y mostramos en consola cada resultado encontrado
store.query(query, (bindings) => console.log(bindings['?who'].value));
*/
query = $rdf.SPARQLToQuery(query_sparql, false, store);
/* --------------------------------------------------
Añadimos un nuevo recurso con información RDF
*/
$('#addRdf').click(async function () {
doc = store.sym($('#container').val() + $('#newRdf').val());
store.add(doc, DC('creator'), $('#webid').val(), doc);
await fetcher.putBack(doc);
$('#newRdf').val('');
fetcher.refresh($('#container').val())
showContainer($('#container').val());
});
store.query(query, function (bindings) {
console.log(bindings['?who'].value);
/* -----------------------------------------------------
Eliminamos recurso a partir de su URI
*/
$('#delete').click(async function () {
if (confirm("¿Seguro que quiere borrar " + $('#deleteUri').val() +"?")) {
await fetcher.delete($('#deleteUri').val());
await fetcher.refresh($('#container').val());
$('#deleteUri').val('');
showContainer($('#container').val());
}
});
*/
\ No newline at end of file
/* -----------------------------------------------------
Hacemos el recurso solo accesible por el propietario
*/
$('#block').click(async function () {
owner = $('#webid').val();
uri = $('#blockUri').val();
rdf = `
@prefix acl: <http://www.w3.org/ns/auth/acl#>.
@prefix foaf: <http://xmlns.com/foaf/0.1/>.
<#owner>
a acl:Authorization;
acl:agent <${owner}>;
acl:accessTo <${uri}>;
acl:mode acl:Read, acl:Write, acl:Control.
`;
console.log(rdf);
await fc.createFile($('#blockUri').val() + '.acl', rdf, 'text/turtle');
$('#blockUri').val('');
await fetcher.refresh($('#container').val())
showContainer($('#container').val());
});
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment