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> <!doctype html>
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>Navegador SOLID</title> <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> </head>
<style> <body>
body {
max-width: 800px; <nav class="navbar navbar-light bg-light">
margin: 0 auto; <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, <div class="container in-session">
input, <div class="row">
button {
font: 11pt/1.3 "Helvetiva Neue", Helvetica, Arial, sans-serif;
}
button, <div class="col-md-10 offset-md-1">
label { <h2>Perfil</h2>
font-weight: bold; <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 { <div class="row">
width: 400px; <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 { <div class="row">
font-weight: bold; <div class="col-md-10 offset-md-1">
clear: left; <h4>Almacenamiento</h4>
float: left; <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 { <div class="row">
content: ': '; <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 { <div class="input-group mb-3">
color: #7c4dff; <input id="newContainer" class="form-control" placeholder="Nombre de la carpeta">
border-bottom: 1px solid; <div class="input-group-append">
cursor: pointer; <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 { <div class="input-group mb-3">
color: #9b79fc; <input id="newRdf" class="form-control" placeholder="Nombre del archivo .ttl">
} <div class="input-group-append">
</style> <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> <label>Eliminar recurso:</label>
<h1>Navegador SOLID</h1>
<div id="login"> <div class="input-group mb-3">
<button>Iniciar sesión</button> <input id="deleteUri" class="form-control" placeholder="URI del recurso">
</div> <div class="input-group-append">
<div id="logout"> <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">
Has iniciado sesión como <strong id="user"></strong>. <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"/>
<button id="logoutButton">Cerrar sesión</button> <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"/>
<h2>Perfil</h2> </svg></button>
<input id="webid" size="60"> <button id="view">Mostrar</button> </div>
<p> </div>
<label>Nombre:</label>
<span id="fullName"></span>
</p>
<p id="photo"></p>
<h2>Contactos</h2> <label>Hacer privado:</label>
<ul id="friends"></ul>
<input id="newFriend" size="60"/> <button id="addFriend">Añadir</button> <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> </div>
<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> </div> <!-- / .container -->
<script src="https://cdn.jsdelivr.net/npm/rdflib@1.1.0/dist/rdflib.min.js"></script>
<script src="js/main.js"></script>
</body>
<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> </html>
\ No newline at end of file
/* ------------------------------------ /* -----------------------------------------
* Gestión de sesiones Inicialización
* ------------------------------------ */ */
$('#login button').click(() => popupLogin());
$('#logout #logoutButton').click(() => solid.auth.logout()); // Creamos un almacén y le asociamos un lector RDF
const store = $rdf.graph();
/* Login */ const fetcher = new $rdf.Fetcher(store);
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
* ------------------------------------ */
// Establecemos espacios de nombres // Establecemos espacios de nombres
const FOAF = $rdf.Namespace('http://xmlns.com/foaf/0.1/'); const FOAF = $rdf.Namespace('http://xmlns.com/foaf/0.1/');
const VCARD = $rdf.Namespace('http://www.w3.org/2006/vcard/ns#'); const VCARD = $rdf.Namespace('http://www.w3.org/2006/vcard/ns#');
const LDP = $rdf.Namespace('http://www.w3.org/ns/ldp#'); 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 // Definimos función para invocar todas subvistas
const store = $rdf.graph(); function showProfile(person) {
const fetcher = new $rdf.Fetcher(store); 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() { function showPersonalData(person) {
// Cargamos los datos a partir del WebID // Mostramos tres campos del perfil
const person = $('#webid').val(); const fullName = store.any($rdf.sym(person), FOAF('name'));
await fetcher.load(person); const photoUrl = store.any($rdf.sym(person), VCARD('hasPhoto'));
showPersonalData(person); const email_node = store.any($rdf.sym(person), VCARD('hasEmail'));
showFriends(person); const email = store.any(email_node, VCARD('value'));
var patt = /https:\/\/[^\/]+\//;
const rootContainer = patt.exec(person) + 'public/';
showContainer(rootContainer);
}
// Información personal $('#fullName').text(fullName && fullName.value);
function showPersonalData(person) { $('#email').html($('<a>').attr('href', email && email.value).text(email && email.value));
// Mostramos tres campos del perfil $('#photo').html($('<img>').attr('src', photoUrl && photoUrl.value).attr('width', '160px'));
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'));
} }
/* ------------------------- /* ------------------------------------
Contactos (navegable) Contactos (navegable)
*/ */
async function showFriends(person) { async function showFriends(person) {
// Mostramos su lista de amigos // Mostramos su lista de amigos
const friends = store.each($rdf.sym(person), FOAF('knows')); const friends = store.each($rdf.sym(person), FOAF('knows'));
$('#friends').empty(); $('#friends').empty();
friends.forEach(async (friend) => { friends.forEach(async (friend) => {
await fetcher.load(friend); // con cada "load", la base de conocimiento ¡crece! await fetcher.load(friend); // cada vez que hacemos "load", nuestra base de conocimiento ¡crece!
const fullName = store.any(friend, FOAF('name')); const fullName = store.any(friend, FOAF('name'));
$('#friends').append( $('#friends').append(
$('<li>').append( $('<li>').append(
$('<a>').text((fullName && fullName.value || friend.value)) $('<button>').text((fullName && fullName.value || friend.value))
.click(() => $('#webid').val(friend.value)) .addClass('btn btn-link')
.click(() => showProfile(friend.value)))); .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) { async function showContainer(container) {
// Cargamos datos desde el POD // Cargamos el contenido del POD
await fetcher.load(container); await fetcher.load(container);
$('#container').val(container); $('#container').val(container);
// Mostramos elementos
const items = store.each($rdf.sym(container), LDP('contains')); // Mostramos elementos
$('#items').empty(); const items = store.each($rdf.sym(container), LDP('contains'));
items.forEach(async (item) => { $('#items').empty();
if (item.value[item.value.length - 1] == '/') items.forEach(async (item) => {
element = $('<a>').text(decodeURI(item.value)) text = item.value.replace(container, ''); // eliminamos ruta
.click(() => $('#container').val(item.value)) if (item.value[item.value.length-1] == '/')
.click(() => showContainer(item.value)); element = $('<button>').text(decodeURI(text))
else .addClass('btn btn-outline-primary')
element = $('<a>').text(decodeURI(item.value)) .click(() => $('#container').val(item.value))
.attr('href', item.value) .click(() => showContainer(item.value));
.attr('target', 'blank'); else
$('#items').append($('<li>').append(element)); 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 () { $('#updateContainer').click(async function () {
await fetcher.createContainer($('#container').val(), $('#newContainer').val(), null); fetcher.refresh($('#container').val())
$('#newContainer').val(''); showContainer($('#container').val());
showContainer($('#container').val());
}); });
/* ----------------------------------------------- /* --------------------------------------------------
* Añadimos un nuevo amigo Añadimos una nueva carpeta (contenedor)
*/ */
$('#addFriend').click(async function () { $('#addContainer').click(async function () {
person = store.sym($('#webid').val()); // nodos await fetcher.createContainer($('#container').val(), $('#newContainer').val(), null);
friend = store.sym($('#newFriend').val()); $('#newContainer').val('');
profile = person.doc(); // este es el documento a actualizar (card) fetcher.refresh($('#container').val())
store.add(person, FOAF('knows'), friend, profile); showContainer($('#container').val());
await fetcher.putBack(profile); // actualizamos documento en la nube
$('#newFriend').val('');
showFriends(person);
}); });
/* --------------------------------------------------
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/> \ // Ejecutamos consulta y mostramos en consola cada resultado encontrado
SELECT ?who WHERE { \ store.query(query, (bindings) => console.log(bindings['?who'].value));
?who foaf:knows <" . $(#webid).val() . " > . \ */
}";
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